<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anson VanDoren</title>
    <description>The latest articles on DEV Community by Anson VanDoren (@ansonvandoren).</description>
    <link>https://dev.to/ansonvandoren</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F298614%2Fceb4a1a9-86ea-445c-8972-422b3ed35ce5.jpeg</url>
      <title>DEV Community: Anson VanDoren</title>
      <link>https://dev.to/ansonvandoren</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ansonvandoren"/>
    <language>en</language>
    <item>
      <title>Solving the 'Broken Calculator' problem the hard way</title>
      <dc:creator>Anson VanDoren</dc:creator>
      <pubDate>Sun, 28 Feb 2021 18:37:32 +0000</pubDate>
      <link>https://dev.to/ansonvandoren/solving-the-broken-calculator-problem-the-hard-way-4a1m</link>
      <guid>https://dev.to/ansonvandoren/solving-the-broken-calculator-problem-the-hard-way-4a1m</guid>
      <description>&lt;h1&gt;
  
  
  I don't normally do LeetCode problems...
&lt;/h1&gt;

&lt;p&gt;Although I really enjoy coding, and frequently make good use of those skills as part of my 9-5 job, I've never really spent much time solving challenges on LeetCode or similar sites. I enjoy the thought exercises, but haven't "needed" the experience to get an edge in software interviews (although whether or not this should actually give anyone an edge is highly debatable), and when I do have time to sit down and write some code it's usually to solve an immediate, practical problem. None of this is to say that I don't or wouldn't enjoy being able to do this, it's just a fact of life for me.&lt;/p&gt;

&lt;p&gt;So I almost ignored an opportunity last weekend when my &lt;a href="https://trevorvandoren.com"&gt;brother&lt;/a&gt; pointed me toward an &lt;a href="https://leetcode.com/explore/challenge/card/february-leetcoding-challenge-2021/586/week-3-february-15th-february-21st/3647/"&gt;interesting entry&lt;/a&gt; and challenged me to submit a solution.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: you will likely have to sign up for an account with LeetCode to see the problem on their website. If you don't feel like doing that, just keep reading - I'll summarize it below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  ...but when I do...
&lt;/h1&gt;

&lt;p&gt;The puzzle was, as seems to often be the case, one that looked easy at first, until you go to write code to solve it. There are several variations on this problem, but here's the version in this particular challenge:&lt;/p&gt;

&lt;p&gt;You have a calculator that is somehow broken so badly that all that's left functional is the screen (which can display numbers between 1 and 1 billion), and two other buttons. Oddly, these buttons are two that don't show up on any calculator I've seen, namely a &lt;strong&gt;double&lt;/strong&gt; and a &lt;strong&gt;decrement&lt;/strong&gt; (by one) operation.&lt;/p&gt;

&lt;p&gt;The challenge is to get from a starting number on the calculator to a given second number using only those two functions.&lt;/p&gt;

&lt;p&gt;The example given is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input: &lt;code&gt;x&lt;/code&gt; = 2, &lt;code&gt;y&lt;/code&gt; = 3&lt;/li&gt;
&lt;li&gt;Output: 2&lt;/li&gt;
&lt;li&gt;Explanation: Use double operation, and then decrement operation [2 -&amp;gt; 4 -&amp;gt; 3]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anyway, calculator vagaries aside, the code needed to solve this puzzle should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Given any starting number &lt;code&gt;x&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Using only the &lt;strong&gt;double&lt;/strong&gt; &lt;code&gt;(x = x * 2)&lt;/code&gt; and/or &lt;strong&gt;decrement&lt;/strong&gt; &lt;code&gt;(x = x - 1)&lt;/code&gt; operations&lt;/li&gt;
&lt;li&gt;Determine the minimum operations needed to arrive at arbitrarily chosen &lt;code&gt;y&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The immediately obvious, and (as far as I can tell) wrong way to look at this problem is to do what's stated, and try to get from &lt;code&gt;x&lt;/code&gt; to &lt;code&gt;y&lt;/code&gt;. Having done a few coding challenges before, I've learned that it's often helpful to start by working backwards from the solution, and that turned out to be useful in this case as well.&lt;/p&gt;

&lt;p&gt;Once I got to this backwards mindset, the rest fell into place pretty quickly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;y&lt;/code&gt; is less than &lt;code&gt;x&lt;/code&gt;:

&lt;ul&gt;
&lt;li&gt;Decrement &lt;code&gt;x&lt;/code&gt; until it equals &lt;code&gt;y&lt;/code&gt; and return the number of operations. There is no doubling in this case.&lt;/li&gt;
&lt;li&gt;A quicker way to look this is to just return &lt;code&gt;x - y&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;y&lt;/code&gt; equals &lt;code&gt;x&lt;/code&gt;, then return 0&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;y&lt;/code&gt; is greater than &lt;code&gt;x&lt;/code&gt;:

&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;y&lt;/code&gt; is an odd number, increment &lt;code&gt;y&lt;/code&gt; by 1 (since we're going backwards, incrementing here is equivalent to decrementing if starting from &lt;code&gt;x&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;y&lt;/code&gt; is an even number:&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;y&lt;/code&gt; is greater than &lt;code&gt;x&lt;/code&gt;, divide &lt;code&gt;y&lt;/code&gt; by 2 (equivalent to doubling &lt;code&gt;x&lt;/code&gt;, one of the two permitted operations).&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;y&lt;/code&gt; is less than &lt;code&gt;x&lt;/code&gt;, then increment &lt;code&gt;y&lt;/code&gt; until you arrive at &lt;code&gt;x&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Loop through this sequence until &lt;code&gt;y&lt;/code&gt; is equal to &lt;code&gt;x&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ends up looking like some variation of the below Python code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# first pass algorithm
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;min_ops&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="n"&gt;ops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;ops&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ops&lt;/span&gt;

&lt;span class="c1"&gt;# testing code
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'__main__'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;test_cases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;test_cases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;case&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;min_ops&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; should have been &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, but got &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Correctly solved &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; operations"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  ...I prefer a more complicated answer...
&lt;/h1&gt;

&lt;p&gt;There's absolutely nothing wrong with this algorithm. It gives the correct answer, it meets LeetCode's criteria for memory usage and runtime, and it's pretty easy to understand.&lt;/p&gt;

&lt;p&gt;However... it really bugged me that I had to iterate through each state of the answer to arrive at the result. For really, really big numbers, this would take a long time. I know, I know...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It doesn't really matter&lt;/li&gt;
&lt;li&gt;The answer is correct&lt;/li&gt;
&lt;li&gt;It meets spec&lt;/li&gt;
&lt;li&gt;Perfect is the enemy of good enough&lt;/li&gt;
&lt;li&gt;Premature optimization is the root of all evil&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;... and all that.&lt;/p&gt;

&lt;p&gt;In real life, I almost certainly would've stopped here unless there was a business reason for optimizing further. But it was the weekend, and although the weather was perfect for spending outside, and I had a ton of other side projects I wanted to get to, it couldn't take too much longer to arrive at a solution I actually liked, right? ... right??&lt;/p&gt;

&lt;p&gt;Many hours and 18 sheets of scratch paper later...&lt;/p&gt;

&lt;h1&gt;
  
  
  Patterns are fun (if you're a nerd)
&lt;/h1&gt;

&lt;p&gt;Here we go.&lt;/p&gt;

&lt;p&gt;Let's start with the case where &lt;code&gt;x&lt;/code&gt; is 1, and see what it takes to get to various &lt;code&gt;y&lt;/code&gt; values:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--omCybvyi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--omCybvyi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-1.png" alt="`x=1` for various `y`"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A few things are definitely going on here:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is an even/odd component, since the last operation for any odd number must be a decrement, and the last operation for any even number may not be a decrement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p65zdoqB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p65zdoqB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-2.png" alt="Even vs odd"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is a "powers of 2" element. Notice that (with &lt;code&gt;x = 1&lt;/code&gt;, at least) the set &lt;code&gt;y = [2, 4, 8, 16, ... 2^n]&lt;/code&gt; is composed solely of double operations, with no decrements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VvEyeUA7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-3.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VvEyeUA7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-3.png%23center" alt="`x * 2^n` has no decrements"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In addition to the above, one can also see that there are the same number of double operations for all numbers in the set &lt;code&gt;y = [2^n + 1...2^(n+1)]&lt;/code&gt;. For example, each of &lt;code&gt;y = [9, 10 ... 15, 16]&lt;/code&gt; has exactly 4 double operations, with varying numbers of decrement operations. The same is true (but with exactly 3 double operations) for &lt;code&gt;y = [5, 6, 7, 8]&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UlTS_-lk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-4.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UlTS_-lk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-4.png%23center" alt="Same number of double operations in each 'group'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comparing the &lt;code&gt;y = [9, 10 ... 15, 16]&lt;/code&gt; set again, it appears that the "upper" half is a repeat of the "lower half", but instead of each row having at least 3 double operations at the start, each has instead two doubles, a decrement, and then another double. Along with this, the "right side" of the pattern from &lt;code&gt;y = [13, 14, 15, 16]&lt;/code&gt; is repeated, but one operation "rightward".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MfcuFipH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-5.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MfcuFipH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-5.png%23center" alt="Repeating blocks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finally, the same pattern at the trailing edge of the &lt;code&gt;y = [9 ... 16]&lt;/code&gt; set is also seen in the &lt;code&gt;y = [5 ... 8]&lt;/code&gt; set, and looks like it might be starting in the &lt;code&gt;y = [3, 4]&lt;/code&gt; set, but isn't complete.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Rjp2S1o6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-6.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Rjp2S1o6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-6.png%23center" alt="Repeating blocks pt. 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  How does this make an algorithm?
&lt;/h1&gt;

&lt;p&gt;So glad you asked...&lt;/p&gt;

&lt;p&gt;Let's start with same condition to handle the case when &lt;code&gt;y&lt;/code&gt; is less than &lt;code&gt;x&lt;/code&gt;. There's not much fun going on in that portion, since it's just a matter of decrementing until you arrive at the result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;min_ops_magic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's take advantage of the fact that all values in &lt;code&gt;y = [2^n + 1...2^(n+1)]&lt;/code&gt; have the same number of double operations. This number of double operations is the same as the power of 2 for the upper bound of that group, or &lt;code&gt;n+1&lt;/code&gt; in the equation above.&lt;/p&gt;

&lt;p&gt;In our example, the set &lt;code&gt;y = [9, 10, ... 15, 16]&lt;/code&gt; contains all numbers such that &lt;code&gt;y &amp;gt; x * 2^3&lt;/code&gt; and &lt;code&gt;y ≤ x * 2^4&lt;/code&gt;. So I'll refer to these numbers as being in the "&lt;code&gt;n == 4&lt;/code&gt;" group. If I use up those 4 operations, I'm left with this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BYIYOxkM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-7.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BYIYOxkM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-7.png%23center" alt="Use up `n == 4` operations"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So what I need to determine is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Given inputs &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt;, what is the smallest &lt;code&gt;n&lt;/code&gt; such that &lt;code&gt;y ≤ x * 2^n&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are a few different ways to code this. I'm also calculating the &lt;code&gt;2^n&lt;/code&gt; at the same time here, since we'll need that later&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;math&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;next_pow2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;pow_of_two&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pow_of_two&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This option works because:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;y = x * 2^n&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;y/x = 2^n&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;log(y/x) = log(2^n)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;log(y/x) = n * log(2)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;log(y/x) / log(2) = n&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;log2(y/x) = n&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Rounding &lt;code&gt;n&lt;/code&gt; up to the next highest integer means we can solve for &lt;code&gt;n&lt;/code&gt; anywhere in the given range.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;next_pow2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;pow_of_two&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pow_of_two&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pow_of_two&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pow_of_two&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This option uses bit-shifting to count how many times we need to shift a bit left (starting from binary &lt;code&gt;1&lt;/code&gt;), which is the same operation as incrementing &lt;code&gt;n&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;0001 == 1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;0010 == 2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;0100 == 4&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;1000 == 8&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Initially I thought that Option 2 would've been faster, but after some timing checks it seems I was wrong, and the &lt;code&gt;log2()&lt;/code&gt; operation was noticeably quicker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 3&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;next_pow2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;bit_length&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;pow_of_two&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This option is similar to Option 2, but it uses a Python built-in method on the integer type to return how many bits are needed to represent the number. The upper limit (16, here) has one more bit than all the other numbers in the group, so I subtract that out first.&lt;/p&gt;

&lt;p&gt;Option 3 was the one I discovered last, and is the quickest of the three. If you have a faster way to calculate this number in Python, I'd love to hear it!&lt;/p&gt;

&lt;p&gt;Let's add Option 3 to our algorithm. I'll inline it since I don't need to call from anywhere else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;min_ops_magic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;

    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;bit_length&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;pow_of_two&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  The "magic", Part 1 - LSBs
&lt;/h1&gt;

&lt;p&gt;OK, this is where I thought things started to get a little weird. I'm sure there's a great mathematical explanation for it, but I can't exactly see it yet. Let me show you how I got to the answer, though.&lt;/p&gt;

&lt;p&gt;If you look back up at the previous diagram, you'll see I grouped the remaining operations in a specific way. There is a J-tetromino (rotated 180°) that's repeated in both the upper and lower halves of the group, and an I-tetromino that only occurs on the top half, and pushes the upper J out by one. And no, the fact that there's Tetris pieces in this puzzle isn't the weird part.&lt;/p&gt;

&lt;p&gt;Based on the work from the previous section, I thought it made sense to number the rows counting upwards and starting at the upper bound. It's probably not clear what I mean, so here's another diagram.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0-KUaEzJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-8.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0-KUaEzJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-8.png%23center" alt="`idx` of each row"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After fiddling around for rather a long time trying to figure out how to calculate the number of operations expressed by each row, I happened to write out the index value out in binary:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xvFNKlOA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-9.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xvFNKlOA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-9.png%23center" alt="`idx` of each row, in binary"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the pattern? I didn't at first, either. Try now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lf1IxMod--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-10.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lf1IxMod--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-10.png%23center" alt="`idx` of each row, in binary, with hints"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's a very tempting, obvious, and generally incorrect solution that involves adding all the "1" bits of the index number to get the remaining operations needed. In fact, it does work for the case I'm showing here, but it doesn't work for all cases. I'll get to that part in a second. Hopefully for now you can just take my word for it, at least for a couple more steps.&lt;/p&gt;

&lt;p&gt;Instead of adding all of the "1" bits together, I only want to add &lt;code&gt;n&lt;/code&gt; least significant bits. I know, I know... in this case that means all of the bits. But I promise that's not the case for all &lt;code&gt;x&lt;/code&gt;s and &lt;code&gt;y&lt;/code&gt;s.&lt;/p&gt;

&lt;p&gt;Let's get some more code going. This is only an additional two lines, so I'll just add it to the algorithm we already have and highlight the changes below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;min_ops_magic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;

    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;bit_length&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;pow_of_two&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;

    &lt;span class="c1"&gt;# count down from top of 'bin'
&lt;/span&gt;    &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pow_of_two&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;

    &lt;span class="c1"&gt;# sum the '1's of the `n` least significant bits
&lt;/span&gt;    &lt;span class="n"&gt;lsb_ones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:].&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Getting the index is fairly straightforward: just subtract &lt;code&gt;Y&lt;/code&gt; from the highest number in this 'bin'.&lt;/p&gt;

&lt;p&gt;Summing up the "1"s in the &lt;code&gt;n&lt;/code&gt; least significant bits is pretty Python-specific, and there's several other ways to do it as well. In Python 3.10 (which should be released this year), there's actually a new &lt;code&gt;int.bit_count()&lt;/code&gt; method which will do exactly this, and much faster. At any rate, this line is just converting the integer &lt;code&gt;idx&lt;/code&gt; to a binary string representation, slicing only the last &lt;code&gt;n&lt;/code&gt; characters of that string, and then counting how many of those characters are &lt;code&gt;"1"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Again, note that even though &lt;em&gt;in this particular case&lt;/em&gt; the number of bits in these numbers happens to be 3 or fewer (for &lt;code&gt;n == 3&lt;/code&gt;), that isn't always the case, so we can't count all of the bits.&lt;/p&gt;

&lt;p&gt;We're very close now. As promised, here's a case where we we need to add one final term to get our total operations:&lt;/p&gt;

&lt;h1&gt;
  
  
  The "magic", Part 2 - MSBs
&lt;/h1&gt;

&lt;p&gt;I chose a somewhat-but-not-quite random &lt;code&gt;x&lt;/code&gt; value, and set of &lt;code&gt;y&lt;/code&gt; values for this example. Feel free to verify this works for other values, though.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Spoiler alert: it does&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's check with &lt;code&gt;x = 10&lt;/code&gt; and the bucket of &lt;code&gt;y = [21, 22 ... 39, 40]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Honrct3l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-11.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Honrct3l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-11.png%23center" alt="Splitting MSB from LSB"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, I've blocked out the &lt;code&gt;n=2&lt;/code&gt; operations, and am left with a few tetronimos, and some big boxes that would be a Tetris nightmare. You can see I've also filled in the &lt;code&gt;idx&lt;/code&gt; values for each row and written them out in binary.&lt;/p&gt;

&lt;p&gt;Remember how I said we can only use the &lt;code&gt;n&lt;/code&gt; least significant bits in the previous calculation? Take a look in this example what would happen if we used all of them. Wrong answers all over the place...&lt;/p&gt;

&lt;p&gt;OK, so if we can only use the &lt;code&gt;n&lt;/code&gt; LSBs for that calculation, why did I write out all the MSBs anyway?&lt;/p&gt;

&lt;p&gt;Only because this is the final piece of the puzzle.&lt;/p&gt;

&lt;p&gt;For the LSBs, we just summed the number of "1" bits to get our J-tetronimos (tetronima? idk...) For the MSBs, though, we're going to keep treating them as real numbers, but only after we've gotten rid of those already-used LSBs.&lt;/p&gt;

&lt;p&gt;You're probably way ahead of me on this, but you can see that if you treat the bits to the left of the vertical line as their own numbers, you get the number of extra columns needed to complete the total operations. Here it is a little clearer:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aeO5HPt---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-12.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aeO5HPt---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/broken-calc-12.png%23center" alt="Treating MSBs as standalone numbers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All that's left is a bit of coding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;min_ops_magic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;

    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;bit_length&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;pow_of_two&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;

    &lt;span class="c1"&gt;# count down from top of 'bin'
&lt;/span&gt;    &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pow_of_two&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;

    &lt;span class="c1"&gt;# sum the '1's of the `n` least significant bits
&lt;/span&gt;    &lt;span class="n"&gt;lsb_ones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:].&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# bit-shift `n` to the right to get only the MSBs
&lt;/span&gt;    &lt;span class="n"&gt;msb_vals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;

    &lt;span class="c1"&gt;# the grand finale
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;lsb_ones&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;msb_vals&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we've already gotten what we needed from the &lt;code&gt;n&lt;/code&gt; LSBs of the index, just shift it right by &lt;code&gt;n&lt;/code&gt; bits to get at the MSBs that we care about. No special conversion needed after that - we're still just treating it as an integer.&lt;/p&gt;

&lt;p&gt;Finally, the answer to the question of life, the universe, and everything (or at least this LeetCode puzzle) is simply the sum of the 2's exponent, the sum of the &lt;code&gt;n&lt;/code&gt; LSB "1" values, and the number represented by the leftover MSBs.&lt;/p&gt;

&lt;p&gt;Crazy, right?&lt;/p&gt;

&lt;h1&gt;
  
  
  But was it worth it?
&lt;/h1&gt;

&lt;p&gt;tldr; yes&lt;/p&gt;

&lt;p&gt;Besides everything I learned from this (including a lot of brushing up on bitshifting math that I ultimately didn't need for the solution anyway), I also ended up with a more satisfying (to me), faster (for most cases) solution. Your results may vary, but on my machine for an arbitrary set of ~80 different input conditions, the "magic" solution is ~20% faster.&lt;/p&gt;

&lt;p&gt;Bonus points if you can see how this explanation relates to &lt;a href="https://en.wikipedia.org/wiki/Hamming_weight"&gt;Hamming weight&lt;/a&gt; and/or to this unrelated &lt;a href="https://blog.csdn.net/fange121/article/details/50087543"&gt;coding challenge&lt;/a&gt; (sorry, I can't find a link in English, but Google Translate works well enough).&lt;/p&gt;

&lt;p&gt;As always, please do let me know if you find any mistakes in the code or explanation, or if you have questions.&lt;/p&gt;

</description>
      <category>python</category>
      <category>leetcode</category>
      <category>puzzle</category>
      <category>pattern</category>
    </item>
    <item>
      <title>Captive Web Portal for ESP8266 with MicroPython - Part 4</title>
      <dc:creator>Anson VanDoren</dc:creator>
      <pubDate>Thu, 02 Jan 2020 14:21:35 +0000</pubDate>
      <link>https://dev.to/ansonvandoren/captive-web-portal-for-esp8266-with-micropython-part-4-20ck</link>
      <guid>https://dev.to/ansonvandoren/captive-web-portal-for-esp8266-with-micropython-part-4-20ck</guid>
      <description>&lt;p&gt;At the end of &lt;a href="https://dev.to/ansonvandoren/captive-web-portal-for-esp8266-with-micropython-part-3-elg"&gt;Part 3&lt;/a&gt; of this series, I had both a DNS and HTTP server up and running on my Wemos D1 Mini MCU to make a captive portal that will let me set up the D1 Mini to join my home WiFi without needing to hardcode the SSID and password on each new project I make.&lt;/p&gt;

&lt;p&gt;All HTTP requests from any client connected to the D1 Mini's WiFi AP are redirected to a web page asking for the home WiFi credentials, but so far nothing happens when you tap the "Connect" button other than being redirected back to the form. In this part, I'll wire up the code to take the submitted SSID and password, and have the MCU try to connect with them.&lt;/p&gt;

&lt;h1&gt;
  
  
  Parsing the form submission
&lt;/h1&gt;

&lt;p&gt;I implemented the HTML form action as a GET request instead of POST. This is really the "wrong" way to do it, but since this is a very small, special-purpose web server, I didn't see any reason to go to the trouble of parsing POST requests as well. So the new WiFi SSID and password will come in as query parameters in a GET request to the &lt;code&gt;/login&lt;/code&gt; path.&lt;/p&gt;

&lt;p&gt;The way I chose to do this is to implement the &lt;code&gt;/login&lt;/code&gt; route as a function instead of a static file. When that route is requested, the function will try to get the SSID and password from the query parameters and save them. Since we also have the opportunity to modify the headers, I'll send a redirect back to the &lt;code&gt;/&lt;/code&gt; path for now. Later in this post, we'll have the HTTP server monitor whether we're connected to my home WiFi or not, and if we are, then it will start serving a different page at the &lt;code&gt;/&lt;/code&gt; route instead of the no-longer-needed login form.&lt;/p&gt;

&lt;p&gt;First, let's modify the &lt;code&gt;get_body()&lt;/code&gt; function so that it knows how to handle a route that's a function instead of a bytestring pointing to a static file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""generate a response body and headers, given a route"""&lt;/span&gt;

        &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;b"HTTP/1.1 200 OK&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# expect a filename, so return contents of file
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rb"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;callable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="c1"&gt;# call a function, which may or may not return a response
&lt;/span&gt;            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s"&gt;b""&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;uio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;

        &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;b"HTTP/1.1 404 Not Found&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;uio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, I'll create a method called &lt;code&gt;login()&lt;/code&gt; to pull the credentials and save them. I'll also add this to my &lt;code&gt;routes&lt;/code&gt; dictionary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;b"/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;b"./index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;b"/login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;saved_credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;ssid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b"ssid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b"password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;saved_credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;b"HTTP/1.1 307 Temporary Redirect&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
            &lt;span class="s"&gt;b"Location: http://{:s}&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;b""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we have some (hopefully) valid WiFi credentials, we need get the captive portal to try to log in to my home WiFi using those.&lt;/p&gt;

&lt;h1&gt;
  
  
  Attempting a WiFi connection
&lt;/h1&gt;

&lt;p&gt;Turning our attention back to the event loop in the &lt;code&gt;CaptivePortal&lt;/code&gt; class, we need to start checking whether the HTTP server has gotten any login credentials. If it does, we should try to log in to the new WiFi.&lt;/p&gt;

&lt;p&gt;I'll add a method that tries to connect to my home WiFi with the provided credentials, if we're not already connected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_portal.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CaptivePortal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_valid_wifi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isconnected&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;has_creds&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="c1"&gt;# have credentials to connect, but not yet connected
&lt;/span&gt;                &lt;span class="c1"&gt;# return value based on whether the connection was successful
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect_to_wifi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="c1"&gt;# not connected, and no credentials to connect yet
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;has_creds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;saved_credentials&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;saved_credentials&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then, call this method on each event loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_portal.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CaptivePortal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;captive_portal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="c1"&gt;# check for socket events and handle them
&lt;/span&gt;                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ipoll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;others&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
                    &lt;span class="n"&gt;is_handled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_dns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;is_handled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_http&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_valid_wifi&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connected to WiFi!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Captive portal stopped"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Go ahead and try this out. If you enter your home WiFi SSID and password correctly, you should see these lines somewhere in the MCU output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;Entering REPL. Use Control-X to exit.
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
MicroPython v1.12 on 2019-12-20&lt;span class="p"&gt;;&lt;/span&gt; ESP module with ESP8266
Type &lt;span class="s2"&gt;"help()"&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;more information.
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; import main
...
Trying to connect to SSID &lt;span class="s1"&gt;'MyHomeWifi'&lt;/span&gt; with password notmyrealpassword
&lt;span class="c"&gt;#15 ets_task(4020f4d8, 28, 3fffa410, 10)&lt;/span&gt;
Connection &lt;span class="k"&gt;in &lt;/span&gt;progress
Connection &lt;span class="k"&gt;in &lt;/span&gt;progress
Connection &lt;span class="k"&gt;in &lt;/span&gt;progress
Connected to MyHomeWifi
Wrote credentials to ./wifi.creds
Connected to WiFi!
Captive portal stopped
Cleaning up
DNS Server stopped
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Updating the servers after connection
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; now that the MCU has successfully connected once, it's saved the credentials to its flash memory and next time you restart the code, it will automatically connect without needing to start up the DNS and HTTP servers. To continue to test the rest of this project, you'll need to delete the &lt;code&gt;/pyboard/wifi.creds&lt;/code&gt; file to actually get the captive portal to start up.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that the MCU is connected to my home WiFi, I need to update the HTTP server with its new IP address on the other network so that redirections continue to work. I can safely shut down the DNS server at this point since I've already redirected to an IP address instead of domain name. Eventually, I'll want to shut down the HTTP server as well, now that the MCU is configured, but I do want to show a "connection successful" page that displays the MCU's new IP address on my home network. &lt;/p&gt;

&lt;p&gt;Let's turn off the DNS server first, since it's no longer doing anything. This is a one-line addition to the captive portal event loop, but we'll also add a line in the same spot to update the HTTP server with its new address and SSID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_portal.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CaptivePortal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;captive_portal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="c1"&gt;# check for socket events and handle them
&lt;/span&gt;                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ipoll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;others&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
                    &lt;span class="n"&gt;is_handled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_dns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;is_handled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_http&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_valid_wifi&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connected to WiFi!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dns_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For the HTTP server, we need to do a few things in the &lt;code&gt;set_ip()&lt;/code&gt; function we're about to write:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change the existing attribute &lt;code&gt;local_ip&lt;/code&gt; to the new IP address. This will cause all redirects to point to the new IP address, which is on my home WiFi network instead of the MCU's access point. This way, when I turn off the MCU's access point (which I'll code up soon), I can still see the status page when my phone reconnects to my home network.&lt;/li&gt;
&lt;li&gt;Create a new property called &lt;code&gt;ssid&lt;/code&gt; that is initially set to &lt;code&gt;None&lt;/code&gt;, and updated to the new SSID after we connect to it.&lt;/li&gt;
&lt;li&gt;Change the &lt;code&gt;routes&lt;/code&gt; dictionary so that after we're connected, the root path (&lt;code&gt;/&lt;/code&gt;) points to a different location, which we'll write as a callable route.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_ssid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""update settings after connected to local WiFi"""&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_ip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_ssid&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;b"/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connected&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the future, I may want a more complicated "connected" page, but for now I just want to display the new SSID the MCU connected to as well as its IP address. To do this, I'm going to make a rudimentary template HTML file, and have the &lt;code&gt;connected()&lt;/code&gt; function fill in those values before serving it. Let's start with a new HTML page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- connected.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Connected!&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Connected!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
      Device is connected to WiFi access point &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;'%s'&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt; with IP
      address &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;%s&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;%s&lt;/code&gt; in two places where I want to fill in the dynamic data: I'll use Python's built-in string formatting methods to convert these into the actual strings I'm looking for after reading in the contents of the HTML file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;connected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;b"HTTP/1.1 200 OK&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./connected.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rb"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Go ahead and test it out. After logging in, you should be redirected to a page showing your home WiFi SSID, and the MCU's new IP address on that network. Additionally, if you look at the address bar in your browser, you should see that it's been updated to the new IP. If you disconnect from the MCU's access point now, and reconnect to your home WiFi, you should see that the page remains available, showing that you are in fact connected.&lt;/p&gt;

&lt;h1&gt;
  
  
  Gracefully exiting
&lt;/h1&gt;

&lt;p&gt;We've basically accomplished what we want to now, and have no more need for the captive portal. I do want to keep the HTTP server up at least temporarily, since I may want to add some configuration options the user can select after the MCU is connected to home WiFi. We can, however, shut down the MCU's access point interface. To make sure the transition is complete, I'll add some time delay before shutting it down.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;check_valid_wifi()&lt;/code&gt; method seems like a good place to put this so that it can coordinate only having one network interface active at a time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_portal.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CaptivePortal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;AP_OFF_DELAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;essid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conn_time_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_valid_wifi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isconnected&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;has_creds&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="c1"&gt;# have credentials to connect, but not yet connected
&lt;/span&gt;                &lt;span class="c1"&gt;# return value based on whether the connection was successful
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect_to_wifi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="c1"&gt;# not connected, and no credentials to connect yet
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ap_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="c1"&gt;# access point is already off; do nothing
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="c1"&gt;# already connected to WiFi, so turn off Access Point after a delay
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conn_time_start&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conn_time_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ticks_ms&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AP_OFF_DELAY&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AP_OFF_DELAY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ticks_diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ticks_ms&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conn_time_start&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ap_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Turned off access point"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Odds and ends
&lt;/h1&gt;

&lt;p&gt;This project is just about finished. A few final notes that may help you get this set up on your own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sometimes either the server, or my phone would screw up the socket connection and I'd get &lt;code&gt;ECONNRESET&lt;/code&gt; errors. Using the built-in function &lt;code&gt;machine.reset()&lt;/code&gt; on the MCU usually fixed this, but sometimes I'd need to completely close the browser app on my phone and start it again to clear out the connection.&lt;/li&gt;
&lt;li&gt;Occasionally, I'd find that the HTTP server was taking forever to respond to requests. I added the &lt;code&gt;@micropython.native&lt;/code&gt; annotation to the &lt;code&gt;HTTPServer.handle()&lt;/code&gt; function which seemed to improve this.&lt;/li&gt;
&lt;li&gt;You may find that the 10 second delay for shutting off the MCU's access point may not be long enough for the redirect to go through. It's easy enough to bump up until you find what works for you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd love to hear your feedback on this project! If made some cool modifications/additions to the code, integrated it with your project, or found some issues, please let me know in the comments, or on the GitHub repo!&lt;/p&gt;




&lt;p&gt;Code: &lt;a href="https://github.com/anson-vandoren/esp8266-captive-portal"&gt;GitHub project repo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>micropython</category>
      <category>esp8266</category>
      <category>http</category>
    </item>
    <item>
      <title>Captive Web Portal for ESP8266 with MicroPython - Part 3</title>
      <dc:creator>Anson VanDoren</dc:creator>
      <pubDate>Wed, 01 Jan 2020 15:13:31 +0000</pubDate>
      <link>https://dev.to/ansonvandoren/captive-web-portal-for-esp8266-with-micropython-part-3-elg</link>
      <guid>https://dev.to/ansonvandoren/captive-web-portal-for-esp8266-with-micropython-part-3-elg</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/ansonvandoren/captive-web-portal-for-esp8266-with-micropython-part-2-10if"&gt;Part 2&lt;/a&gt; of this series, I finished implementing the "captive portal" DNS server so that after connecting to my Wemos D1 Mini MCU's WiFi access point, all DNS queries pointed toward the MCU's IP address instead of the actual IP address for the requested domain. I'm implementing this captive portal as a way to be able to configure the MCU to be able to log into my home WiFi without hardcoding the SSID and password, so what I need to do next is set up a HTTP server that will present a form where I can fill out these details.&lt;/p&gt;

&lt;p&gt;So far, the &lt;code&gt;CaptivePortal&lt;/code&gt; class creates the DNS Server and registers its socket with a poller that listens for data on the socket stream. This was fairly straightforward since UDP (which DNS uses) is connectionless, and I didn't need to keep track of whether a connection was fully closed or not. I'll do something similar for the HTTP server, but will need to keep track of which connections are incoming and outgoing, and which are closed. This makes things a little more complicated, but I'll start with my base server class, and proceed step by step from there.&lt;/p&gt;

&lt;h1&gt;
  
  
  Basic HTTP server with sockets
&lt;/h1&gt;

&lt;p&gt;Create a new file called &lt;code&gt;captive_http.py&lt;/code&gt; and add the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;usocket&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;server&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"HTTP Server"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;local_ip&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# queue up to 5 connection requests before refusing
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setblocking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# client connecting on port 80, so spawn off a new
&lt;/span&gt;            &lt;span class="c1"&gt;# socket to handle this connection
&lt;/span&gt;            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"- Accepting new HTTP connection"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POLLIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# socket has data to read in
&lt;/span&gt;            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"- Reading incoming HTTP data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POLLOUT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# existing connection has space to send more data
&lt;/span&gt;            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"- Sending outgoing HTTP data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here's the basis for how the HTTP server will look. It has a &lt;code&gt;handle()&lt;/code&gt; method just like the DNS server did, but in this case we need to consider three different cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The client wants to initiate a new request from the HTTP server. In this case the event will come in over the original "server" socket, on port 80. I'll handle this in the &lt;code&gt;accept()&lt;/code&gt; method by spawning off a new "client" socket to handle that particular request/response. &lt;br&gt;
&lt;em&gt;Note: "client socket" in this case refers to a socket belonging to the server, but which is spawned to handle a specific client request. By contrast, the "server socket" in this discussion is only responsible for accepting new connections and creating a "client socket" for them, but doesn't itself respond to any request.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An existing connection (on a client socket) has sent us more data. This is signified by a &lt;code&gt;POLLIN&lt;/code&gt; event on the client socket, and calls the &lt;code&gt;read()&lt;/code&gt; method to read in the data from the socket until we get to the end of the full message.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An existing connection (on a client socket) is ready for us to send more data to the client. This is signified with a &lt;code&gt;POLLOUT&lt;/code&gt; event on the client socket, and I'll handle it in the &lt;code&gt;write_to()&lt;/code&gt; method by sending the next set of data for that connection.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One point to keep in mind here is that regardless of the actual size of the incoming or outgoing messages, we'll only be sending segments of 536 bytes at a time; this is the default &lt;a href="https://en.wikipedia.org/wiki/Maximum_segment_size"&gt;maximum segment size&lt;/a&gt; for TCP/IP.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For reading data in from the socket, this is relatively easy: just read all data that's sent until we get to the end. For this server, we only need to handle HTTP GET requests, so we can assume that if we find a blank line in the data (&lt;code&gt;\r\n\r\n&lt;/code&gt;), it is the end of the message. If we needed to handle POST requests as well, we'd need to read in the &lt;code&gt;Content-Length&lt;/code&gt; or &lt;code&gt;Transfer-Encoding: Chunked&lt;/code&gt; headers to determine how to know when the request was finished.&lt;/li&gt;
&lt;li&gt;For writing data out to the socket, we need to break the response up into chunks of 536 bytes each. After sending each chunk, we'll advance a pointer to where the next chunk of data will be, then return and wait for the poller to let us know the socket is available to send more data. Once there's no more data left to write, we close the socket.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Accepting a new socket connection
&lt;/h2&gt;

&lt;p&gt;Let's start with the easiest case: accepting a new connection. Here, we already know what socket to expect the connection request on, since it's the same socket we created at initialization ("server socket"). The &lt;code&gt;socket.accept()&lt;/code&gt; method will spawn off a new socket (the "client socket") to use for this connection and return the new client socket as well as the destination address to which it's paired.&lt;/p&gt;

&lt;p&gt;Once I have the new client socket, I set it to non-blocking and reusable (like the server socket), and then add it to the list of open streams on which poller will listen for events.&lt;/p&gt;

&lt;p&gt;At this point, the server socket goes back to listening for new incoming connections, and the newly-created client socket will do the work of reading in the request and writing out a response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;uerrno&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;uselect&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server_sock&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""accept a new client request socket and register it for polling"""&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;client_sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server_sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;OSError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;uerrno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EAGAIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="n"&gt;client_sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setblocking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;client_sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setsockopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOL_SOCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SO_REUSEADDR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POLLIN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Reading from a client socket
&lt;/h2&gt;

&lt;p&gt;Once we're spawned off a client socket for an incoming connection request, and set it up for polling, we should expect a subsequent &lt;code&gt;POLLIN&lt;/code&gt; event (or several) where the client is sending us data that we need to read in. The way we're building this project, we can assume that any &lt;code&gt;POLLIN&lt;/code&gt; event that doesn't belong to the DNS socket is going to be a client socket for the HTTP server, so we can just pass them all to our new &lt;code&gt;read()&lt;/code&gt; method below.&lt;/p&gt;

&lt;p&gt;Calling the incoming socket's &lt;code&gt;read()&lt;/code&gt; method gives us some amount of data, but we don't know upfront whether it's the full message or a partial message. If there's no data, it's a good sign that we already hit the end of the message, and we should just close the socket now.&lt;/p&gt;

&lt;p&gt;Otherwise, let's check if we're already partway through an incoming message for this client socket. If we are, add the new data to the existing partial message, otherwise create an empty bytestring and append the data.&lt;/p&gt;

&lt;p&gt;Here, we're going to cheat a little bit since we only need to handle GET requests, and we can assume that any blank line signifies the end of the message. If the last four bytes of the incoming data is not &lt;code&gt;\r\n\r\n&lt;/code&gt;, we expect there's more to come, so return early and wait for the next &lt;code&gt;POLLIN&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;If we did find a blank line at the end, get the full request and print it out so we can take a look. The client will be expecting a message back, so for now let's just queue up a &lt;code&gt;404 Not Found&lt;/code&gt; message to get them off our backs until we can code up a way to send real HTML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;uio&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""read in client request from socket"""&lt;/span&gt;

        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# no data in the TCP stream, so close the socket
&lt;/span&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="c1"&gt;# add new data to the full request
&lt;/span&gt;        &lt;span class="n"&gt;sid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;b""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;

        &lt;span class="c1"&gt;# check if additional data expected
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;b"&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# HTTP request is not finished if no blank line at the end
&lt;/span&gt;            &lt;span class="c1"&gt;# wait for next read event on this socket instead
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="c1"&gt;# get the completed request
&lt;/span&gt;        &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Client raw request:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# send a 404 response for now
&lt;/span&gt;        &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;b"HTTP/1.1 404 Not Found&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prepare_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Sending to a client socket
&lt;/h2&gt;

&lt;p&gt;Now all that's left to complete the HTTP transaction is to write a response back to the client. Remember that we're limited to writing 536 bytes at a time, but our response may be longer than that. In the previous section, after reading in the request, we called &lt;code&gt;prepare_write()&lt;/code&gt; with the headers and body we wanted to send back to the client. We'll need a way to keep track of how much data we've written out for each response, and where we should start writing next time the same socket is ready to accept more of our response.&lt;/p&gt;

&lt;p&gt;To help keep track of all that, I made a &lt;code&gt;namedtuple&lt;/code&gt; for the writer connection that contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The full response body we need to send&lt;/li&gt;
&lt;li&gt;A buffer that will contain only the next 536 (maximum) bytes we plan to send&lt;/li&gt;
&lt;li&gt;A memoryview of the buffer to help with writing in and out of the buffer without needless copying&lt;/li&gt;
&lt;li&gt;A range for starting and ending bytes of the buffer that we want to send next.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice that I'm not keeping a copy of the headers, since they'll always (in our case) be less than 536 bytes and will always go out with the first message segment.&lt;/p&gt;

&lt;p&gt;With all that in place, I'm adding an extra newline to the headers to get a blank line (required between headers and body), and then writing the headers into the buffer (padded to make the buffer size 536 bytes). Then I use the memoryview to read from the body into the buffer, up to a maximum total of 536 bytes in the buffer. The &lt;code&gt;readinto()&lt;/code&gt; call tells me how many bytes were actually read from body into the buffer, and I use that number to set the end point of the &lt;code&gt;write_range&lt;/code&gt;. I save the &lt;code&gt;WriteConn&lt;/code&gt; information into the &lt;code&gt;self.conns&lt;/code&gt; dictionary with a key of the socket's ID so I can look it up later when I need to write the remainder of the message.&lt;/p&gt;

&lt;p&gt;Lastly, I let the poller know that I'm prepared to write out to the socket, so it can let me know when the socket is available for outgoing data. This means I'll start getting &lt;code&gt;POLLOUT&lt;/code&gt; events for this socket, which I'll need to handle to actually start writing this data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;namedtuple&lt;/span&gt;
&lt;span class="n"&gt;WriteConn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;namedtuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"WriteConn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"buff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"buffmv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"write_range"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prepare_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# add newline to headers to signify transition to body
&lt;/span&gt;        &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
        &lt;span class="c1"&gt;# TCP/IP MSS is 536 bytes, so create buffer of this size and
&lt;/span&gt;        &lt;span class="c1"&gt;# initially populate with header data
&lt;/span&gt;        &lt;span class="n"&gt;buff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytearray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;536&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="c1"&gt;# use memoryview to read directly into the buffer without copying
&lt;/span&gt;        &lt;span class="n"&gt;buffmv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;memoryview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# start reading body data into the memoryview starting after
&lt;/span&gt;        &lt;span class="c1"&gt;# the headers, and writing at most the remaining space of the buffer
&lt;/span&gt;        &lt;span class="c1"&gt;# return the number of bytes written into the memoryview from the body
&lt;/span&gt;        &lt;span class="n"&gt;bw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readinto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffmv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:],&lt;/span&gt; &lt;span class="mi"&gt;536&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="c1"&gt;# save place for next write event
&lt;/span&gt;        &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WriteConn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buffmv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;bw&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
        &lt;span class="c1"&gt;# let the poller know we want to know when it's OK to write
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POLLOUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that the poller is set to pass &lt;code&gt;POLLOUT&lt;/code&gt; events to this client socket, we need to handle those events, which will trigger a call to our new &lt;code&gt;write_to()&lt;/code&gt; method. First, we need to get the writer connection back, which will have everything we need to determine which bytes should be written out. Remember that the &lt;code&gt;write_range&lt;/code&gt; value of this tuple is a list of start and end positions of the buffer that we should write:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the buffer is full, this would normally be &lt;code&gt;[0, 536]&lt;/code&gt;, and we'd write the entire contents of the buffer.&lt;/li&gt;
&lt;li&gt;If the buffer is only partially full, the second value in the list would be something less than 536.&lt;/li&gt;
&lt;li&gt;If we got interrupted while writing last time, the first value may be something higher than 0, and we'e start writing from that position instead of 0.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After writing out to the socket, we check how much was written. If we didn't write any bytes, or we had less than 536 bytes to write, we're done sending this response, and we can close the socket.&lt;/p&gt;

&lt;p&gt;Otherwise, we wrote all the bytes in the buffer, so we need to keep the socket open, advance the buffer to the next bytes of the response body so that when the next &lt;code&gt;POLLOUT&lt;/code&gt; event comes in, we'll be prepared to continue writing; this part happens in the &lt;code&gt;buff_advance()&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""write the next message to an open socket"""&lt;/span&gt;

        &lt;span class="c1"&gt;# get the data that needs to be written to this socket
&lt;/span&gt;        &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# write next 536 bytes (max) into the socket
&lt;/span&gt;            &lt;span class="n"&gt;bytes_written&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buffmv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;bytes_written&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;536&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# either we wrote no bytes, or we wrote &amp;lt; TCP MSS of bytes
&lt;/span&gt;                &lt;span class="c1"&gt;# so we're done with this connection
&lt;/span&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# more to write, so read the next portion of the data into
&lt;/span&gt;                &lt;span class="c1"&gt;# the memoryview for the next send event
&lt;/span&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buff_advance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes_written&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;buff_advance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes_written&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""advance the writer buffer for this connection to next outgoing bytes"""&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bytes_written&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="c1"&gt;# wrote all the bytes we had buffered into the memoryview
&lt;/span&gt;            &lt;span class="c1"&gt;# set next write start on the memoryview to the beginning
&lt;/span&gt;            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="c1"&gt;# set next write end on the memoryview to length of bytes
&lt;/span&gt;            &lt;span class="c1"&gt;# read in from remainder of the body, up to TCP MSS
&lt;/span&gt;            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readinto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;536&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# didn't read in all the bytes that were in the memoryview
&lt;/span&gt;            &lt;span class="c1"&gt;# so just set next write start to where we ended the write
&lt;/span&gt;            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;bytes_written&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Closing a client socket
&lt;/h2&gt;

&lt;p&gt;There were a few cases above where we needed to close a client socket, either because we were done writing to it, or else done reading from it. Note that we never close the server socket (until we stop our program), since we want to be able to continue listening for incoming connection requests.&lt;/p&gt;

&lt;p&gt;To cleanly close a client socket, we also need to unregister it from the poller, and delete any remaining request or connection data we had stored for it. Since we have very limited RAM on the ESP8266, we do this manually and then call the garbage collector to make sure we're conserving every single byte of RAM as early as possible to avoid memory errors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;gc&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""close the socket, unregister from poller, and delete connection"""&lt;/span&gt;

        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unregister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sid&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sid&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Adding the HTTP server to our CaptivePortal class
&lt;/h1&gt;

&lt;p&gt;Now that we have the skeleton of the HTTP server set up, let's instantiate one in the &lt;code&gt;CaptivePortal&lt;/code&gt; class and add it to the polling loop. We can test that we're actually reading in data correctly before we move on to actually correctly handling requests.&lt;/p&gt;

&lt;p&gt;We previously wrote our &lt;code&gt;handle_dns()&lt;/code&gt; method so that it returned False if it didn't handle the stream event, and True if it did. We'll make use of this here to make sure the HTTP server doesn't try to handle events that don't belong to it or one of its child client sockets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_portal.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;captive_dns&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DNSServer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;captive_http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HTTPServer&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CaptivePortal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;essid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dns_server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;captive_portal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting captive portal"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_access_point&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_server&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Configured HTTP server"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dns_server&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dns_server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DNSServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Configured DNS server"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="c1"&gt;# check for socket events and handle them
&lt;/span&gt;                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ipoll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;others&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
                    &lt;span class="n"&gt;is_handled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_dns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;is_handled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_http&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Captive portal stopped"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_http&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Time to test it out. Copy the code to the MCU and start it up. Connect to the MCU's access point with your phone, and then open a browser and try to navigate to a &lt;strong&gt;non-HTTPS&lt;/strong&gt; page (&lt;a href="http://neverssl.com"&gt;http://neverssl.com&lt;/a&gt;, &lt;a href="http://example.com"&gt;http://example.com&lt;/a&gt;, for example).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;Entering REPL. Use Control-X to exit.
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
MicroPython v1.12 on 2019-12-20&lt;span class="p"&gt;;&lt;/span&gt; ESP module with ESP8266
Type &lt;span class="s2"&gt;"help()"&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;more information.
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; import main
Trying to load WiFi credentials from ./wifi.creds
./wifi.creds does not exist
Starting captive portal
Waiting &lt;span class="k"&gt;for &lt;/span&gt;access point to turn on
&lt;span class="c"&gt;#37 ets_task(4020f510, 29, 3fff8f88, 10)&lt;/span&gt;
AP mode configured: &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'192.168.4.1'&lt;/span&gt;, &lt;span class="s1"&gt;'255.255.255.0'&lt;/span&gt;, &lt;span class="s1"&gt;'192.168.4.1'&lt;/span&gt;, &lt;span class="s1"&gt;'192.168.4.1'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
HTTP Server listening on &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0.0.0.0'&lt;/span&gt;, 80&lt;span class="o"&gt;)&lt;/span&gt;
Configured HTTP server
DNS Server listening on &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0.0.0.0'&lt;/span&gt;, 53&lt;span class="o"&gt;)&lt;/span&gt;
Configured DNS server
Sending connectivitycheck.gstatic.com. -&amp;gt; 192.168.4.1
- Accepting new HTTP connection
- Reading incoming HTTP data
Client raw request:
 b&lt;span class="s1"&gt;'GET /generate_204 HTTP/1.1\r\nUser-Agent: Dalvik/2.1.0 (Linux; U; Android 6.0.1;
 Nexus 7 Build/MOB30X)\r\nHost: connectivitycheck.gstatic.com\r\nConnection:
 Keep-Alive\r\nAccept-Encoding: gzip\r\n\r\n'&lt;/span&gt;
- Sending outgoing HTTP data
Captive portal stopped
Cleaning up
DNS Server stopped
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This looks good. We can see that the DNS server is redirecting all domains to the MCU's IP address, and then the HTTP server is accepting the connection and reading in the request data. This particular request is my Android device checking whether it has active internet or not. iOS devices do something similar, but with a different endpoint. If you wanted to trick the device into thinking it actually had an internet connection, all you'd need to do is respond to this request with a &lt;code&gt;204 No Content&lt;/code&gt; response. If you're curious how this works, &lt;a href="https://success.tanaza.com/s/article/How-Automatic-Detection-of-Captive-Portal-works"&gt;this article&lt;/a&gt; has some additional detail.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting up routes and serving actual responses
&lt;/h1&gt;

&lt;p&gt;So far, we're responding to all requests with a &lt;code&gt;404 Not Found&lt;/code&gt; header and an empty body. Obviously we're going to need to send real responses to make this work. There's two things we need to get started with this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Some actual content in the form of HTML files.&lt;/li&gt;
&lt;li&gt;A way to examine the request, get the path requested, and map that to a specific HTML file.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Captive portal landing HTML page
&lt;/h2&gt;

&lt;p&gt;Since the goal of this project is to let me enter my home WiFi credentials so the MCU can connect to it, I'll need a simple HTML page with a form on it where I can enter those details. I created a file called &lt;code&gt;index.html&lt;/code&gt; with a basic form and a bit of inline styling. Since this is an extremely simple page on a very basic server, I'm OK with using inline CSS instead of a separate file to make serving things simpler for me.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- index.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;WiFi Login&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#3498db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#525252&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nc"&gt;.box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;'text'&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
      &lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;'password'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ecf0f1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ccc&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ccc&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#aaa&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nc"&gt;.btn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2ecc71&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40ch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;padding-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;padding-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#27ae60&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;800&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.8em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/login"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"get"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"box"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;WiFi login credentials&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"My WiFi SSID"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ssid"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;br&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
        &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"WiFi Password"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;br&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Connect&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Mapping files to HTTP paths
&lt;/h2&gt;

&lt;p&gt;Now that I have a HTML file, I need to tell my server which HTTP paths should serve that file. Let's add a &lt;code&gt;routes&lt;/code&gt; dictionary to the &lt;code&gt;HTTPServer&lt;/code&gt; class. If I was making a more general-purpose HTTP server, I'd probably want to create a method for adding routes, and let the parent create the routes instead of having the server do it directly. In this case, however, I'll have a very limited number of routes that will never change, so I'm just going to create them through a dictionary literal in the HTTPServer constructor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;b"/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;b"./index.html"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  HTTP routing based on requested path
&lt;/h2&gt;

&lt;p&gt;Now that we have a file created, and know what route should serve that file, we just need to examine each incoming request to see what path is being requested, and respond appropriately.&lt;/p&gt;

&lt;p&gt;First, let's parse the HTTP request and pull out the details we may be interested in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;ReqInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;namedtuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ReqInfo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""parse a raw HTTP request to get items of interest"""&lt;/span&gt;

        &lt;span class="n"&gt;req_lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;req_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http_ver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req_lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b" "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;full_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b"?"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;base_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;query_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b"="&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b"&amp;amp;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b": "&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;req_lines&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;b"Host:"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ReqInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="c1"&gt;# get the completed request
&lt;/span&gt;        &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="c1"&gt;# send a 404 response for now
&lt;/span&gt;        &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;b"HTTP/1.1 404 Not Found&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prepare_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then, I'll create a helper function to take the parsed request and either return either the contents of the HTML file if the route matches, or else an empty byte string. Add the following to &lt;code&gt;captive_html.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""generate a response body and headers, given a route"""&lt;/span&gt;

        &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;b"HTTP/1.1 200 OK&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# expect a filename, so return contents of file
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rb"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;

        &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;b"HTTP/1.1 404 Not Found&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;uio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If the route is found, I'll get a &lt;code&gt;bytes&lt;/code&gt; string with the path to the HTML file, and I'll open that file (in binary mode) and return the file stream object.&lt;/p&gt;

&lt;p&gt;If the route is not found, I'll get a &lt;code&gt;None&lt;/code&gt; from the dictionary, and instead I'll return an empty bytes stream object. In both cases, I'll return appropriate headers along with the body.&lt;/p&gt;

&lt;p&gt;Let's modify the &lt;code&gt;read()&lt;/code&gt; function to try to get a body depending on the route, and also generate a correct header depending on whether the route was matched or not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="c1"&gt;# get the completed request
&lt;/span&gt;        &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prepare_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Time to test it out. Copy all the new/changed files over to the MCU, and fire it up.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; don't forget to copy your new &lt;code&gt;index.html&lt;/code&gt; file onto the MCU as well, along with the &lt;code&gt;.py&lt;/code&gt; files. Place the HTML files in the same folder (&lt;code&gt;/pyboard&lt;/code&gt;) as the others.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once the captive portal is running, connect to the MCU's WiFi access point and navigate to &lt;code&gt;http://192.168.4.1/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cIm0iysE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/anson-vandoren/ansonvandoren.com/raw/master/static/images/esp8266_captive_portal_index_page.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cIm0iysE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/anson-vandoren/ansonvandoren.com/raw/master/static/images/esp8266_captive_portal_index_page.png" alt="Captive portal login page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Actually, if you navigate to any HTTP-only site, at the root path, you'll get the same page since our DNS server is telling the client that all domains point to the MCU's IP address. You can test this by navigating to any site that doesn't use HTTPS (try &lt;a href="http://neverssl.com"&gt;http://neverssl.com&lt;/a&gt; to test). This is definitely progress, but still not quite what we want. If (for example), I tried navigating to &lt;a href="http://neverssl.com/online"&gt;http://neverssl.com/online&lt;/a&gt;, I'd get a &lt;code&gt;404 Not Found&lt;/code&gt; back from the server instead of it redirecting me to the login page I want. Additionally, it kind of bugs me that the domain I tried navigating to still shows up in the browser address bar instead of the MCU's IP address.&lt;/p&gt;

&lt;h1&gt;
  
  
  Redirecting to the captive portal
&lt;/h1&gt;

&lt;p&gt;Both of these issues can be basically solved in the same way: if the requested host or path from the client are not the ones I want to serve, I can send a redirect response back to the client to point them where I want them to go. This response will just be an empty body with a &lt;code&gt;307 Temporary Redirect&lt;/code&gt; header.&lt;/p&gt;

&lt;p&gt;Let's modify the &lt;code&gt;HTTPServer.read()&lt;/code&gt; method to check for a valid request. If the requested host matches the MCU's IP address, and the route is known, then serve the page for that route. Otherwise, return a redirect response pointing to the root path of the MCU's IP address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="c1"&gt;# get the completed request
&lt;/span&gt;        &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_valid_req&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;b"HTTP/1.1 307 Temporary Redirect&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
                &lt;span class="s"&gt;b"Location: http://{:s}/&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prepare_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="c1"&gt;# by this point, we know the request has the correct
&lt;/span&gt;        &lt;span class="c1"&gt;# host and a valid route
&lt;/span&gt;        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prepare_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then we can write the &lt;code&gt;is_valid_req()&lt;/code&gt; method like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_http.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_valid_req&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# force a redirect to the MCU's IP address
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="c1"&gt;# redirect if we don't have a route for the requested path
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routes&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Go ahead and test that out. Any non-HTTPS domain you try, with any path, should redirect you back to &lt;code&gt;http://192.168.4.1&lt;/code&gt;, which matches the route for the index page with the login form.&lt;/p&gt;

&lt;h1&gt;
  
  
  Recap
&lt;/h1&gt;

&lt;p&gt;We've made progress, but still don't have a functioning product yet. The D1 Mini has a working DNS server to point all domain requests to the local IP address, and a HTTP server that will redirect all unknown hosts and paths to the root path of the local IP address, which will now serve a form asking for the WiFI SSID and password where we want the D1 Mini to connect in the future.&lt;/p&gt;

&lt;p&gt;All that's left is to actually parse the form submission, have the D1 Mini try to connect to the new WiFi, and then let the user know if it was successful. This is stretching into a longer writeup than I was anticipating, but I didn't want to skimp too much on the details, or make any post too long to comfortably follow. &lt;/p&gt;

</description>
      <category>python</category>
      <category>micropython</category>
      <category>esp8266</category>
      <category>sockets</category>
    </item>
    <item>
      <title>Captive Web Portal for ESP8266 with MicroPython - Part 2</title>
      <dc:creator>Anson VanDoren</dc:creator>
      <pubDate>Tue, 31 Dec 2019 12:18:36 +0000</pubDate>
      <link>https://dev.to/ansonvandoren/captive-web-portal-for-esp8266-with-micropython-part-2-10if</link>
      <guid>https://dev.to/ansonvandoren/captive-web-portal-for-esp8266-with-micropython-part-2-10if</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/ansonvandoren/captive-web-portal-for-esp8266-with-micropython-part-1-d56"&gt;Part 1&lt;/a&gt; of this series, I had set up the beginnings of a "captive portal" DNS server. The intent of this server is to redirect all DNS requests to point to the IP address of the Wemos D1 Mini MCU that's running the WiFi access point. See the intro to that post for why I want to do this.&lt;/p&gt;

&lt;p&gt;I'd progressed to the point where I had a working socket that could display the raw datagram coming in over port 53 for DNS queries when I connected my phone to the MCU's AP. Now I need to understand how to interpret those requests, and how to construct a DNS answer I can send back to the client that will redirect them to the IP address I want.&lt;/p&gt;

&lt;h1&gt;
  
  
  Understanding DNS requests
&lt;/h1&gt;

&lt;p&gt;I don't want to go into excruciating detail here about how DNS messages are constructed, but it is pretty important to understand in order to make this work. I relied pretty heavily on a great article by &lt;a href="https://github.com/jamesroutley"&gt;James Routley&lt;/a&gt; that you &lt;a href="https://routley.io/posts/hand-writing-dns-messages/"&gt;can find here&lt;/a&gt;. I'd suggest reading it for the details, and then come back to see how I implemented it for my captive portal. The &lt;a href="https://en.wikipedia.org/wiki/Domain_Name_System#DNS_message_format"&gt;Wikipedia article&lt;/a&gt; also has some good amplifying information that was pretty useful. If you just can't get enough of DNS nitty-gritty, you could also try reading &lt;a href="https://tools.ietf.org/html/rfc2929"&gt;RFC 2929&lt;/a&gt; which is the internet standard for DNS messages.&lt;/p&gt;

&lt;h1&gt;
  
  
  DNS request example
&lt;/h1&gt;

&lt;p&gt;Here's an example of the request my DNS server logged as incoming from my phone when it first connected:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;b'\x99\xa5\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03ssl\x07gstatic\x03com\x00\x00\x01\x00\x01'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Python is showing me the message in byte format (&lt;code&gt;b''&lt;/code&gt;), which converts to ASCII where it can, and displays the raw hexadecimal value of each byte where it can't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request Header
&lt;/h2&gt;

&lt;p&gt;The message header is 12 bytes long; in this example the header is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\x99\xa5\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first two bytes are the request ID generated by the client. As the server, I don't really care what this ID is, except that I need to remember it and include it in the response back to the client.

&lt;ul&gt;
&lt;li&gt;In this example, the ID is &lt;code&gt;\x99\xa5&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;The second two bytes (16 bits) are the Flags section:

&lt;ul&gt;
&lt;li&gt;In this example, the Flags are &lt;code&gt;\x01\x00&lt;/code&gt;, or in binary &lt;code&gt;0000000100000000&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;See Wikipedia article linked above for a full explanation of what each bit means.&lt;/li&gt;
&lt;li&gt;In this case, the binary representation of these two bytes (in &lt;a href="https://en.wikipedia.org/wiki/Endianness"&gt;big-endian&lt;/a&gt; ("network byte") format) means all header flags are set to 0 except for the &lt;code&gt;RD&lt;/code&gt; bit, or "Recursion Desired".&lt;/li&gt;
&lt;li&gt;Cloudflare has a &lt;a href="https://www.cloudflare.com/learning/dns/what-is-recursive-dns/"&gt;good article&lt;/a&gt; explaining recursive vs. iterative DNS requests if you're interested. For our part, we don't really care about this since we're only redirecting all DNS requests to our captive portal anyway.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;The rest of the header (&lt;code&gt;\x00\x01\x00\x00\x00\x00\x00\x00&lt;/code&gt;) gives the number of questions, answers, authority records, and additional records in the message. I'll ignore them for my purposes, and assume the client is only sending one question (which is true in this case).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Request Question
&lt;/h2&gt;

&lt;p&gt;The question section has a variable length for the first part (&lt;code&gt;QNAME&lt;/code&gt;) and a fixed two bytes for each of the remaining two parts (&lt;code&gt;QTYPE&lt;/code&gt; and &lt;code&gt;QCLASS&lt;/code&gt;). All I really care about for this project is the &lt;code&gt;QNAME&lt;/code&gt;, which is the domain the client wants an IP address for, as I'll need that to construct the answer response. This section contains one or more "labels" where the first byte tells how many bytes the full label is, and the rest of the label is part of the domain being queried.&lt;/p&gt;

&lt;p&gt;In this example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First byte tells how long the upcoming label is (&lt;code&gt;\x03&lt;/code&gt; for the first label)&lt;/li&gt;
&lt;li&gt;The next 3 bytes (&lt;code&gt;ssl&lt;/code&gt; for the first label) is the label name. Since these bytes are valid ASCII characters, Python has converted it for me automatically.&lt;/li&gt;
&lt;li&gt;After each label in this section, there is an implicit &lt;code&gt;.&lt;/code&gt;, which I'll need to add.&lt;/li&gt;
&lt;li&gt;Repeat until label length is &lt;code&gt;\x00&lt;/code&gt;, which marks end of the QNAME section&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This &lt;code&gt;QNAME&lt;/code&gt; fully resolves to &lt;code&gt;ssl.gstatic.com&lt;/code&gt;, after which there is a zero-byte (&lt;code&gt;\x00&lt;/code&gt;) telling me the &lt;code&gt;QNAME&lt;/code&gt; section is done.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The next byte pair is &lt;code&gt;QTYPE&lt;/code&gt;, which in this case is &lt;code&gt;\x00\x01&lt;/code&gt; for an A record. &lt;a href="https://en.wikipedia.org/wiki/List_of_DNS_record_types"&gt;This Wikipedia article&lt;/a&gt; lists other possible options for record types.&lt;/li&gt;
&lt;li&gt;The final byte pair is &lt;code&gt;QCLASS&lt;/code&gt;, which in this case is &lt;code&gt;\x00\x01&lt;/code&gt; for internet (IN).&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Parsing a DNS question
&lt;/h1&gt;

&lt;p&gt;Let's create a new class in our &lt;code&gt;captive_dns.py&lt;/code&gt; file to handle DNS queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_dns.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DNSQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
        &lt;span class="c1"&gt;# header is bytes 0-11, so question starts on byte 12
&lt;/span&gt;        &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
        &lt;span class="c1"&gt;# length of this label defined in first byte
&lt;/span&gt;        &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="c1"&gt;# add the label to the requested domain and insert a dot after
&lt;/span&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt;
            &lt;span class="c1"&gt;# check if there is another label after this one
&lt;/span&gt;            &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When we create a new &lt;code&gt;DNSQuery&lt;/code&gt;, we'll pass in the raw datagram we got from the socket and store it. What we really are trying to get at to start with is the requested domain, so we don't need to parse the header information yet (although we will need to for the response).&lt;/p&gt;

&lt;p&gt;To get the domain out of the message, we start on byte 12, read the length of that label, and then read that many bytes into the domain, appending a &lt;code&gt;.&lt;/code&gt; to each label. When we get to a length-byte of 0, we know we have the full requested domain.&lt;/p&gt;

&lt;p&gt;Now let's update our &lt;code&gt;DNSServer&lt;/code&gt; class to parse the incoming requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_dns.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DNSServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# server doesn't spawn other sockets, so only respond to its own socket
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="c1"&gt;# check the DNS question, and respond with an answer
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recvfrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DNSQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Client requested IP address for domain:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# help MicroPython with memory management
&lt;/span&gt;            &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
            &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DNS server exception:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If I run this now, and reconnect my phone to the MCU access point, I'll see something like this with the resolved domain name questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;MicroPython v1.12 on 2019-12-20&lt;span class="p"&gt;;&lt;/span&gt; ESP module with ESP8266
Type &lt;span class="s2"&gt;"help()"&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;more information.
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; import main
Trying to load WiFi credentials from ./wifi.creds
./wifi.creds does not exist
Starting captive portal
Waiting &lt;span class="k"&gt;for &lt;/span&gt;access point to turn on
&lt;span class="c"&gt;#57 ets_task(4020f510, 29, 3fff8f88, 10)&lt;/span&gt;
AP mode configured: &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'192.168.4.1'&lt;/span&gt;, &lt;span class="s1"&gt;'255.255.255.0'&lt;/span&gt;, &lt;span class="s1"&gt;'192.168.4.1'&lt;/span&gt;, &lt;span class="s1"&gt;'192.168.4.1'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
DNS Server listening on &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0.0.0.0'&lt;/span&gt;, 53&lt;span class="o"&gt;)&lt;/span&gt;
Configured DNS server
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: connectivitycheck.gstatic.com.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: encrypted-tbn0.gstatic.com.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: www.google.com.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: android.googleapis.com.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: zowkbggnyjedl.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: uxfwdospee.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: dwcccklrboesuuf.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: encrypted-tbn0.gstatic.com.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: www.google.com.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: update.googleapis.com.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: inbox.google.com.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: encrypted-tbn0.gstatic.com.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: www.google.com.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: connectivitycheck.gstatic.com.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: android.googleapis.com.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: mtalk.google.com.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: zowkbggnyjedl.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: uxfwdospee.
Client requested IP address &lt;span class="k"&gt;for &lt;/span&gt;domain: dwcccklrboesuuf.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Notice groups of 3 "garbage" domain requests interspersed between the legitimate ones. It took me quite a while to figure out some google terms that would lead me to an answer as to what those were (winning answer was "gobbledygook dns queries"), but it turns out those are Chrome trying to prevent DNS hijacking by an ISP. There's a pretty good &lt;a href="https://unix.stackexchange.com/a/363513/197269"&gt;StackExchange answer&lt;/a&gt; if you're curious for more details. In our case it is pretty irrelevant since we aren't trying to actually correct DNS results, and we don't care that Chrome/Chromium knows about it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Understanding DNS responses
&lt;/h1&gt;

&lt;p&gt;Now that we know what domain the client is asking for, it's time to generate a response. Since the point of the captive portal is to redirect all DNS requests back to the MCU's IP address, we don't care about what the correct answer is. This makes it a lot simpler to finish out this section of the project.&lt;/p&gt;

&lt;p&gt;If you need to, refer back to the links at the beginning of the post to see the details about the response format, since I'll just gloss over it here. The response contains all the same sections as the request (Header, Question), but it will also contain an Answer section that we'll need to construct here. The Question will be identical, but we'll also need to modify the Header section a bit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Response Header
&lt;/h2&gt;

&lt;p&gt;Again, we're not trying to construct a necessarily correct response, just a valid one so the client can process it. What we need in the header section is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set first two bytes to the ID that came with the request, so the client knows which request this response belongs to.&lt;/li&gt;
&lt;li&gt;Cheat a little, and set the flags section without actual regard for request. For every answer, we'll set the flags to &lt;code&gt;\x81\x80&lt;/code&gt;, which translates to &lt;code&gt;1000000110000000&lt;/code&gt; in binary and means:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;QR&lt;/code&gt;=1 (this is a response, not a query)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Opcode&lt;/code&gt;=0 (this is a response to a standard query)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TC&lt;/code&gt;=0 (this response is not truncated)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RD&lt;/code&gt;=1 (makes assumption client also set this bit on for a recursive lookup, which is normal)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RA&lt;/code&gt;=1 (telling client recursive lookup is available)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Copy the question count from request and set answer count to the same&lt;/li&gt;
&lt;li&gt;Set authority records and additional records to 0&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Response Question and Answer
&lt;/h2&gt;

&lt;p&gt;The body of our response will repeat back the question that was asked and then add an answer on the end. We already have the question stored in our DNSQuery class (raw data starting on byte 12 and going to the end), so we can copy that over easily.&lt;/p&gt;

&lt;p&gt;To construct the answer, we need a few parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A pointer back to the requested domain name. This pointer is two bytes long, with the first two bits being 1s, and the remaining 14 bits being an unsigned integer that specifies the number of bytes from the beginning of the message where the prior occurrence of the name can be found. In our case, that's byte 12.&lt;/li&gt;
&lt;li&gt;The next two byte pairs are &lt;code&gt;TYPE&lt;/code&gt; and &lt;code&gt;CLASS&lt;/code&gt; (similar to &lt;code&gt;QTYPE&lt;/code&gt; and &lt;code&gt;QCLASS&lt;/code&gt; in the question section). We'll set these for an A record and IN class, regardless of the question, since that's all our simple server knows how to handle (and also by far the most common request we'll get).&lt;/li&gt;
&lt;li&gt;The next byte pair is TTL in seconds, as a 32-bit number. This tells the client how long this response is valid for, and I chose 60sec, or &lt;code&gt;\x00\x00\x00\x3C&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Then comes the length (in bytes) of the response body. Since I'm returning an IPv4 address, this fits into 4 bytes, so I used &lt;code&gt;\x00\x04&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Finally, I break down the IP address into 4 bytes and send them as the final bytes in the packet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using the example from the request section above, here's what my response looks like, broken down:&lt;/p&gt;

&lt;p&gt;Header: &lt;code&gt;\x99\xa5\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Question: &lt;code&gt;\x03ssl\x07gstatic\x03com\x00&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Answer: &lt;code&gt;\x00\x01\x00\x01\xc0\x0c\x00\x01\x00\x01\x00&amp;lt;\x00\x04\xc0\xa8\x04\x01&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note the odd-looking behavior in our TTL octets: we actually sent &lt;code&gt;\x00\x00\x00\x3C&lt;/code&gt;, but when printed to the screen it shows as &lt;code&gt;\x00\x00\x00&amp;lt;&lt;/code&gt; instead. This is because Python automatically tries to convert ASCII characters it knows about into the ASCII equivalent. &lt;code&gt;\x3c&lt;/code&gt; is decimal 60, which is ASCII &lt;code&gt;&amp;lt;&lt;/code&gt; character. This is perfectly normal, and the response is still correct in terms of bytes; it took me a while to figure out that this is just a Python internal representation when I was looking at the output of my program here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Creating a DNS response
&lt;/h1&gt;

&lt;p&gt;Now that we know the format of a response, let's update our &lt;code&gt;DNSQuery&lt;/code&gt; class to generate one when asked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_dns.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DNSQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip_addr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# ** create the answer header **
&lt;/span&gt;        &lt;span class="c1"&gt;# copy the ID from incoming request
&lt;/span&gt;        &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# set response flags (assume RD=1 from request)
&lt;/span&gt;        &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;b"&lt;/span&gt;&lt;span class="se"&gt;\x81\x80&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
        &lt;span class="c1"&gt;# copy over QDCOUNT and set ANCOUNT equal
&lt;/span&gt;        &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# set NSCOUNT and ARCOUNT to 0
&lt;/span&gt;        &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;b"&lt;/span&gt;&lt;span class="se"&gt;\x00\x00\x00\x00&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;

        &lt;span class="c1"&gt;# ** create the answer body **
&lt;/span&gt;        &lt;span class="c1"&gt;# respond with original domain name question
&lt;/span&gt;        &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
        &lt;span class="c1"&gt;# pointer back to domain name (at byte 12)
&lt;/span&gt;        &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;b"&lt;/span&gt;&lt;span class="se"&gt;\xC0\x0C&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
        &lt;span class="c1"&gt;# set TYPE and CLASS (A record and IN class)
&lt;/span&gt;        &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;b"&lt;/span&gt;&lt;span class="se"&gt;\x00\x01\x00\x01&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
        &lt;span class="c1"&gt;# set TTL to 60sec
&lt;/span&gt;        &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;b"&lt;/span&gt;&lt;span class="se"&gt;\x00\x00\x00\x3C&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
        &lt;span class="c1"&gt;# set response length to 4 bytes (to hold one IPv4 address)
&lt;/span&gt;        &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;b"&lt;/span&gt;&lt;span class="se"&gt;\x00\x04&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
        &lt;span class="c1"&gt;# now actually send the IP address as 4 bytes (without the "."s)
&lt;/span&gt;        &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip_addr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

        &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;packet&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here, I'm passing in an IP address (the MCU's address in this case), and getting back a (hopefully) valid DNS answer pointing all domains to that IP.&lt;/p&gt;

&lt;p&gt;With that in place, it's trivial to have the DNS server send back the answer to each question. Make the following addition to the &lt;code&gt;DNSServer&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_dns.py
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DNSServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# server doesn't spawn other sockets, so only respond to its own socket
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="c1"&gt;# check the DNS question, and respond with an answer
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recvfrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DNSQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sending {:s} -&amp;gt; {:s}"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ip_addr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sendto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ip_addr&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# help MicroPython with memory managament
&lt;/span&gt;            &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
            &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DNS server exception:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Fire it up and test everything out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;Entering REPL. Use Control-X to exit.
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
MicroPython v1.12 on 2019-12-20&lt;span class="p"&gt;;&lt;/span&gt; ESP module with ESP8266
Type &lt;span class="s2"&gt;"help()"&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;more information.
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; import main
Trying to load WiFi credentials from ./wifi.creds
./wifi.creds does not exist
Starting captive portal
Waiting &lt;span class="k"&gt;for &lt;/span&gt;access point to turn on
&lt;span class="c"&gt;#73 ets_task(4020f510, 29, 3fff8f88, 10)&lt;/span&gt;
AP mode configured: &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'192.168.4.1'&lt;/span&gt;, &lt;span class="s1"&gt;'255.255.255.0'&lt;/span&gt;, &lt;span class="s1"&gt;'192.168.4.1'&lt;/span&gt;, &lt;span class="s1"&gt;'192.168.4.1'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
DNS Server listening on &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0.0.0.0'&lt;/span&gt;, 53&lt;span class="o"&gt;)&lt;/span&gt;
Configured DNS server
Sending connectivitycheck.gstatic.com. -&amp;gt; 192.168.4.1
Sending connectivitycheck.gstatic.com. -&amp;gt; 192.168.4.1
Sending mtalk.google.com. -&amp;gt; 192.168.4.1
Sending alt5-mtalk.google.com. -&amp;gt; 192.168.4.1
Sending android.googleapis.com. -&amp;gt; 192.168.4.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The output looks like things are working, but we don't have a real way to test right now. Next up, we'll write a HTTP server to see that the client is actually getting redirected where we want.&lt;/p&gt;

&lt;h1&gt;
  
  
  Recap
&lt;/h1&gt;

&lt;p&gt;We can now parse a DNS request to figure out what domain name the client is asking about, and then create a (fake) response that will direct them to the D1 Mini's IP address. In the next section, we'll create a HTTP server on the MCU to make use of the HTTP requests that are currently being routed there by our DNS server.&lt;/p&gt;

</description>
      <category>python</category>
      <category>micropython</category>
      <category>esp8266</category>
      <category>dns</category>
    </item>
    <item>
      <title>Captive Web Portal for ESP8266 with MicroPython - Part 1</title>
      <dc:creator>Anson VanDoren</dc:creator>
      <pubDate>Mon, 30 Dec 2019 21:09:19 +0000</pubDate>
      <link>https://dev.to/ansonvandoren/captive-web-portal-for-esp8266-with-micropython-part-1-d56</link>
      <guid>https://dev.to/ansonvandoren/captive-web-portal-for-esp8266-with-micropython-part-1-d56</guid>
      <description>&lt;h1&gt;
  
  
  Project idea
&lt;/h1&gt;

&lt;p&gt;I've worked on a few small projects recently using an ESP8266 SoC, and one thing that sort of bothered me is that I needed to hard-code my home WiFi access point SSID and password into either the Python file or a separate config file. It's not really that big of a deal for small personal projects, but when I buy "smart" gadgets from anywhere else, they always come with the option to set up the device by connecting to a temporary WiFi access point on the device itself, and then typing my home WiFi SSID and password into a HTML form.&lt;/p&gt;

&lt;p&gt;This aim of this project is to program the ESP8266 MCU to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On startup, first check if it already knows how to log into my home WiFi. Once I set up a device, I'd expect it to remember the WiFi credentials I told it even if it was rebooted.&lt;/li&gt;
&lt;li&gt;If it doesn't have any known credentials, or the previously saved credentials don't work, then start an unsecured WiFi access point on bootup instead.&lt;/li&gt;
&lt;li&gt;With my phone, connect to the MCU WiFi AP, and then enter the SSID and password for my actual home WiFI.&lt;/li&gt;
&lt;li&gt;The MCU then tries to connect to my home WiFi using the newly provided credentials, and display a basic status page showing what network it connected to, and what is its IP address on my home network.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This idea isn't really new, and it's referred to as a "captive portal" in other contexts. It's something you may have seen when logging into public WiFi at your favorite coffee shop, where you're redirected to a sign-in page before being allowed online. There's a few different ways to accomplish that, but the way I'm going to do it will require a HTTP server (to serve some HTML to ask for WiFi SSID and password), and also a DNS server to redirect all DNS questions to the IP address of the MCU so that I don't need to know the board's IP address. I'll be able to just connect to the board's access point, and then navigate to any (non-HTTPS) website, and it will redirect me to the login page I want.&lt;/p&gt;

&lt;p&gt;The non-HTTPS part is important; for the sake of simplicity, the HTTP server is only listening on port 80, and not 443. HTTPS requests will still get redirected to the board, but there isn't a HTTPS socket listening for them there, so they'll time out. It's possible to add HTTPS redirection like this, but there's a few problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Many browsers will display an warning, or refuse to show the page, if a HTTPS site is redirected. This is obviously good for security, but makes it a pain for this particular use-case.&lt;/li&gt;
&lt;li&gt;I would need to set up SSL certificates, start a third socket server on the MCU, and be able to handle TLS/SSL connections, which is more involved than plain HTTP. I &lt;em&gt;think&lt;/em&gt; the ESP8266 is probably capable of this, but it's a tiny device with limited resources available, and this would be way overkill for what I actually need.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For my Android phone (and I think for iOS devices as well), the OS detects that it doesn't have internet access and will automatically ask me to sign in anyway, which goes over HTTP and does the redirection I need. If this doesn't work, I could just intentionally navigate to a site that I know uses HTTP only (like &lt;a href="http://example.com"&gt;http://example.com&lt;/a&gt; or &lt;a href="http://neverssl.com"&gt;http://neverssl.com&lt;/a&gt;) to trigger the redirection.&lt;/p&gt;

&lt;h1&gt;
  
  
  Basic hardware setup
&lt;/h1&gt;

&lt;p&gt;I wrote a &lt;a href="https://ansonvandoren.com/posts/esp8266-with-micropython/"&gt;previous article&lt;/a&gt; on setting up a NodeMCU ESP8266 with MicroPython a few months ago, so instead of repeating all the instructions here you can check out that article instead. I'll assume that you can already &lt;code&gt;rshell&lt;/code&gt; into your MCU for the rest of this post.&lt;/p&gt;

&lt;p&gt;The only difference from last time is that I'm using a different board this time, a &lt;a href="https://amzn.to/2t5AvWi"&gt;Wemos D1 Mini&lt;/a&gt; I ordered from Amazon. It has the same ESP8266 chipset and mostly the same features, but fewer GPIO pins than the other one. The price is about the same either way, but I was looking for a smaller form factor for my next project, and this is about half the size of the &lt;a href="https://amzn.to/2J9CRrJ"&gt;NodeMCU development board&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0mBwArFK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/anson-vandoren/ansonvandoren.com/raw/master/static/images/wemos-d1-mini.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0mBwArFK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/anson-vandoren/ansonvandoren.com/raw/master/static/images/wemos-d1-mini.jpg" alt="Wemos D1 Mini (ESP-8266EX SoC)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The one additional step I did need to get the D1 Mini to work was to install an additional driver. All of my search results for this ended up with kind of sketchy-looking websites, but I think the &lt;a href="http://www.wch.cn/download/CH341SER_MAC_ZIP.html"&gt;"official" driver&lt;/a&gt; from the manufacturer should be safe. The link above is for the macOS version of the driver, but there's also drivers for &lt;a href="http://www.wch.cn/downloads/CH341SER_ZIP.html"&gt;Windows&lt;/a&gt; and &lt;a href="http://www.wch.cn/downloads/CH341SER_LINUX_ZIP.html"&gt;Linux&lt;/a&gt; on the same site. &lt;/p&gt;

&lt;p&gt;Aside from this, everything was basically the same except that the Wemos D1 Mini shows up on a different serial port, in my case &lt;code&gt;/dev/cu.wchusbserial1430&lt;/code&gt;. As a reminder, you can check this yourself using the esptool.py tool &lt;a href="https://github.com/espressif/esptool"&gt;from Espressif&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;esptool.py read_mac
esptool.py v2.8
Found 3 serial ports
Serial port /dev/cu.wchusbserial1430
Connecting....
Detecting chip type... ESP8266
Chip is ESP8266EX
Features: WiFi
Crystal is 26MHz
MAC: 2c:f4:32:78:d0:c1
Uploading stub...
Running stub...
Stub running...
MAC: 2c:f4:32:78:d0:c1
Hard resetting via RTS pin...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  ESP8266 filesystem
&lt;/h1&gt;

&lt;p&gt;Connecting to the board with &lt;code&gt;rshell&lt;/code&gt; after initially flashing the latest MicroPython binary &lt;a href="https://micropython.org/download#esp8266"&gt;from here&lt;/a&gt; (I used v1.12) shows a single file (boot.py) in the &lt;code&gt;/pyboard&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;I didn't touch the boot.py file, and mine looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# /pyboard/boot.py
# This file is executed on every boot (including wake-boot from deepsleep)
#import esp
#esp.osdebug(None)
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;uos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;
&lt;span class="c1"&gt;#uos.dupterm(None, 1) # disable REPL on UART(0)
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;gc&lt;/span&gt;
&lt;span class="c1"&gt;#import webrepl
#webrepl.start()
&lt;/span&gt;&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The boot.py file is the first file run each time the board resets. It's not doing much in my case other than a garbage collection, but I'll leave it as is.&lt;/p&gt;

&lt;p&gt;The other file that's run on each boot is called main.py, but it doesn't exist yet. Since this project is intended to just be the bootstrap code for a new device, I don't want to clutter up main.py with the captive portal code. I'll write most of this code in separate files, and just import and call it from a couple of lines in main.py.&lt;/p&gt;

&lt;p&gt;One other important fact I learned on this project is that since MicroPython needs to import and "compile" (into frozen bytecode) each file in one chunk, the raw Python file sizes need to be limited to what's available in RAM on the MCU, minus what the interpreter is using up. In practice, I found this meant that I couldn't import a source file much larger than ~8Kb. In some cases (for this and other projects in MicroPython), I've needed to run garbage collection between imports as well. The &lt;a href="https://pycopy.readthedocs.io/en/latest/reference/constrained.html#control-of-garbage-collection"&gt;documentation&lt;/a&gt; talks a bit about it if you want more details. If you get (cryptic, seemingly incomplete) errors like the example below, it may mean your file size is too large, and you should break it up into multiple files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; import captive_http
Traceback &lt;span class="o"&gt;(&lt;/span&gt;most recent call last&lt;span class="o"&gt;)&lt;/span&gt;:
  File &lt;span class="s2"&gt;"&amp;lt;stdin&amp;gt;"&lt;/span&gt;, line 1, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
MemoryError:

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Getting started
&lt;/h1&gt;

&lt;p&gt;It's possible to use &lt;code&gt;rshell&lt;/code&gt; to edit files directly (sort of) on the MCU, but I prefer to edit from my host machine and then copy the files in from there during testing. This way I keep the latest updates inside my git repo on my desktop and never need to worry about which is the latest version. My workflow looks something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Edit files (e.g., main.py) on my host PC&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rshell&lt;/code&gt; to MCU, then &lt;code&gt;cp main.py /pyboard/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;On the MCU, start the &lt;code&gt;repl&lt;/code&gt; and then &lt;code&gt;import main&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To start this project off, I'm going to create two files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;main.py&lt;/code&gt;, which will kick off the code I want to run and otherwise be available for future project code.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;captive_portal.py,&lt;/code&gt; which will coordinate the HTTP and DNS servers to make this WiFi bootstrapping code work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll start with &lt;code&gt;main.py&lt;/code&gt; since it's only a couple of lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;captive_portal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CaptivePortal&lt;/span&gt;

&lt;span class="n"&gt;portal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CaptivePortal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;portal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see, not much going on here other than importing my CaptivePortal class and running its &lt;code&gt;start()&lt;/code&gt; function. I'll write that class and method in a new file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_portal.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;network&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;uerrno&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;uos&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;utime&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CaptivePortal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;CRED_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"./wifi.creds"&lt;/span&gt;
    &lt;span class="n"&gt;MAX_CONN_ATTEMPTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta_if&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WLAN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STA_IF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# turn off station interface to force a reconnect
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;try_connect_from_file&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;captive_portal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;connect_to_wifi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Trying to connect to SSID '{:s}' with password {:s}"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# initiate the connection
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MAX_CONN_ATTEMPTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isconnected&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connection in progress"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connected to {:s}"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ifconfig&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_creds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to connect to {:s} with {:s}. WLAN status={:d}"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="c1"&gt;# forget the credentials since they didn't work, and turn off station mode
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write_creds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CRED_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'wb'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b','&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Wrote credentials to {:s}"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CRED_FILE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;captive_portal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting captive portal"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;try_connect_from_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Trying to load WiFi credentials from {:s}"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CRED_FILE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CRED_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;OSError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;uerrno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ENOENT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{:s} does not exist"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CRED_FILE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="n"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CRED_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'rb'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b','&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Invalid credentials file:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect_to_wifi&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connect with saved credentials failed, starting captive portal"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CRED_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;There's a few things that may look strange here if you're coming from CPython ("normal" Python) and haven't used MicroPython before. MicroPython is fairly full-featured, but it is still only a subset of CPython, and as such some of the standard library is missing. In most of these cases, I'm importing the "micro" version of the library instead (like &lt;code&gt;uos&lt;/code&gt; instead of &lt;code&gt;os&lt;/code&gt;). The &lt;a href="https://pycopy.readthedocs.io/en/latest/index.html"&gt;PyCopy docs&lt;/a&gt; are really excellent when trying to figure out similarities and differences between the two Python versions and has been a huge help throughout this project.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note: PyCopy is a fork of MicroPython, but the documentation is much better written and more complete. Generally they are feature equivalent, so I tend to use the docs from PyCopy instead.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The class &lt;code&gt;__init__()&lt;/code&gt; method sets up variables for eventual SSID and password, and also a reference to the MCU's station interface. The station interface is for the MCU to connect to another WiFi hotspot. Later on, we'll also configure the MCU's access point interface.&lt;/p&gt;

&lt;p&gt;For now, the &lt;code&gt;start()&lt;/code&gt; method is just trying to connect to my home WiFi point from previously-saved credentials if it can, and if not, it will (eventually) start the captive portal itself.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;try_connect_from_file()&lt;/code&gt; method is fairly straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check if the file exists where we expect WiFi credentials&lt;/li&gt;
&lt;li&gt;If it does, open it and check that it has two comma separated values&lt;/li&gt;
&lt;li&gt;If it does, try to connect with those values assuming the first is my home WiFi SSID, and the second is the WiFi password&lt;/li&gt;
&lt;li&gt;If any step fails, return &lt;code&gt;False&lt;/code&gt; so that we can start up the captive portal instead to prompt the user to enter these credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;connect_to_wifi()&lt;/code&gt; turns on the station interface and tries to connect with the credentials we have (which don't exist yet, since we didn't read them from the file and haven't prompted the user). It waits up to 20 seconds for the connection to be established before bailing out. If it fails to connect, it will print the interface status, which will be one of the constants listed &lt;a href="https://pycopy.readthedocs.io/en/latest/library/network.WLAN.html#network.WLAN.status"&gt;in the MicroPython &lt;code&gt;network&lt;/code&gt; documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Copy both of these files into the &lt;code&gt;/pyboard/&lt;/code&gt; folder on the MCU, then run &lt;code&gt;repl&lt;/code&gt; and import &lt;code&gt;main&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;/pyboard&amp;gt; repl
Entering REPL. Use Control-X to exit.
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
MicroPython v1.12 on 2019-12-20&lt;span class="p"&gt;;&lt;/span&gt; ESP module with ESP8266
Type &lt;span class="s2"&gt;"help()"&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;more information.
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; import main
Trying to load WiFi credentials from ./wifi.creds
./wifi.creds does not exist
Starting captive portal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Base server class
&lt;/h1&gt;

&lt;p&gt;Since I'll need both a DNS and HTTP server, it makes sense to extract out any common parts into a superclass. The basic functionality of this class will be to create a socket on a specified port and then register it with a stream poller that will notify the server when a new event occurs on the socket. MicroPython has the &lt;a href="https://pycopy.readthedocs.io/en/latest/library/uselect.html"&gt;uselect module&lt;/a&gt;  to help deal with streams like this, which is a subset of CPython &lt;code&gt;select&lt;/code&gt; module. Using this poller, we can run both the HTTP and DNS servers at the same time without either one blocking waiting to listen to its socket.&lt;/p&gt;

&lt;p&gt;Here's the Server class I created in a new file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# server.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;usocket&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;uselect&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sock_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
        &lt;span class="c1"&gt;# create socket with correct type: stream (TCP) or datagram (UDP)
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sock_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# register to get event updates for this socket
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;poller&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POLLIN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getaddrinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0.0.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# allow new requests while still sending last response
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setsockopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOL_SOCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SO_REUSEADDR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"listening on"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unregister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"stopped"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The HTTP server will use a TCP socket on port 80, while the DNS server will use a UDP socket on port 53. Once the socket is created, I'll register it with a poller passed in from the caller (so I can reuse the same poller) and sign up for the &lt;code&gt;POLLIN&lt;/code&gt; event, which will notify whenever the socket has new incoming data to read. Even though I didn't register for them in the event bitmask, I'll also potentially get &lt;code&gt;POLLHUP&lt;/code&gt; (hangup) and &lt;code&gt;POLLERR&lt;/code&gt; (error) events.&lt;/p&gt;

&lt;p&gt;Next I set the socket options to reuse its address. This is mostly only important for the TCP socket since there may still be outgoing data for a recently-closed socket while I want to send new data out on a new connection. For a better in-depth discussion on the &lt;code&gt;SO_REUSEADDR&lt;/code&gt; flag, here's a great &lt;a href="https://stackoverflow.com/questions/14388706/how-do-so-reuseaddr-and-so-reuseport-differ"&gt;StackOverflow answer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, I bind the new socket to the shortcut address "0.0.0.0" which will listen for all IPv4 addresses on the MCU and the given port. The MicroPython documentation for &lt;a href="https://pycopy.readthedocs.io/en/latest/library/usocket.html"&gt;usocket&lt;/a&gt; is helpful in getting this setup correctly.&lt;/p&gt;

&lt;p&gt;Other than the constructor, the only method I pulled up into this superclass is one to gracefully halt the server when I'm done by unregistering it from the poller and closing the socket.&lt;/p&gt;

&lt;h1&gt;
  
  
  DNS server class
&lt;/h1&gt;

&lt;p&gt;Now that I have the skeleton in place, I'll create a DNS server subclass in a new file called &lt;code&gt;captive_dns.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_dns.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;usocket&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;gc&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;server&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DNSServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip_addr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;53&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_DGRAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"DNS Server"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ip_addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ip_addr&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# server doesn't spawn other sockets, so only respond to its own socket
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="c1"&gt;# check the DNS question, and respond with an answer
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recvfrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Got data:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"from sender:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DNS server exception:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This DNS server doesn't really do much yet. Whenever its &lt;code&gt;handle()&lt;/code&gt; method is called with a socket, event type, and possibly additional data, it will check to make sure the socket is what it expects, and then try to read in 1024 bytes of data from the socket. Obviously we'll add more here soon, but this should be enough to test the basic functionality.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting up the access point
&lt;/h1&gt;

&lt;p&gt;Now, back in the &lt;code&gt;CaptivePortal&lt;/code&gt; class, I'll instantiate a DNS server and set up an event poller loop to start listening to the socket stream. Before we can do that, though, I need to set up the MCU to turn on its access point interface so it can accept incoming WiFi connections:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_portal.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;network&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;ubinascii&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;binascii&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CaptivePortal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;AP_IP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"192.168.4.1"&lt;/span&gt;
    &lt;span class="n"&gt;CRED_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"./wifi.creds"&lt;/span&gt;
    &lt;span class="n"&gt;MAX_CONN_ATTEMPTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;essid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AP_IP&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta_if&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WLAN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STA_IF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ap_if&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WLAN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AP_IF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;essid&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;essid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;b"ESP8266-%s"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;binascii&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hexlify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ap_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mac"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;essid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;essid&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here, I'm setting up a local IP address that I'll use for the access point (which happens to be the default ESP8266 address, but doesn't need to be), and then getting access to the Access Point interface. I also added a constructor parameter for the access point SSID in case I wanted to name it differently. If I don't pass in a special name, it will use part of the MCU's MAC address to create a unique SSID for the access point.&lt;/p&gt;

&lt;p&gt;Next, I'll create a method to actually turn on and configure the access point interface, and then call that at the start of the &lt;code&gt;captive_portal()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_portal.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CaptivePortal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_access_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# sometimes need to turn off AP before it will come up properly
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ap_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ap_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Waiting for access point to turn on"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ap_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# IP address, netmask, gateway, DNS
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ap_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ifconfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"255.255.255.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ap_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;essid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;essid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AUTH_OPEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AP mode configured:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ap_if&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ifconfig&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;captive_portal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting captive portal"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_access_point&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I ran into issues occasionally where if the AP interface was already turned on when I tried to reconfigure it, it would throw an error or else just never configure itself properly. To get around that, I explicitly restart it every time I want it on, and wait for it to report it's active before proceeding.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ifconfig()&lt;/code&gt; function call sets (in order):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IP address&lt;/li&gt;
&lt;li&gt;Netmask&lt;/li&gt;
&lt;li&gt;Gateway&lt;/li&gt;
&lt;li&gt;DNS server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The values I'm using for IP address, netmask, and gateway are all the default for the interface, but I'm changing the DNS server to point to the MCU itself, instead of a "real" DNS service so that we can have the MCU respond to all DNS queries for devices that connect to it in order to redirect them how we want.&lt;/p&gt;

&lt;p&gt;Lastly, I set the network SSID (the name I'll see when I try to connect from my phone), and set it to an open access point so I don't need to enter a password to connect. Then it's just a matter of calling this function at the beginning of my &lt;code&gt;captive_portal()&lt;/code&gt; method to kick things off.&lt;/p&gt;

&lt;p&gt;To test everything out so far, I copied my new/changed files onto the MCU and then imported main from the REPL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;MicroPython v1.12 on 2019-12-20&lt;span class="p"&gt;;&lt;/span&gt; ESP module with ESP8266
Type &lt;span class="s2"&gt;"help()"&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;more information.
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; import main
Trying to load WiFi credentials from ./wifi.creds
./wifi.creds does not exist
Starting captive portal
Waiting &lt;span class="k"&gt;for &lt;/span&gt;access point to turn on
&lt;span class="c"&gt;#33 ets_task(4020f510, 29, 3fff8f88, 10)&lt;/span&gt;
&lt;span class="c"&gt;#34 ets_task(4020f510, 29, 3fff8f88, 10)&lt;/span&gt;
AP mode configured: &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'192.168.4.1'&lt;/span&gt;, &lt;span class="s1"&gt;'255.255.255.0'&lt;/span&gt;, &lt;span class="s1"&gt;'192.168.4.1'&lt;/span&gt;, &lt;span class="s1"&gt;'192.168.4.1'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Looks good here. On my phone, I searched for available WiFi access points, and there it is!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OJ_0wveG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/anson-vandoren/ansonvandoren.com/raw/master/static/images/esp8266_captive_portal_access_point.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OJ_0wveG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/anson-vandoren/ansonvandoren.com/raw/master/static/images/esp8266_captive_portal_access_point.png" alt="ESP8266 WiFi Access Point"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Polling for socket events
&lt;/h1&gt;

&lt;p&gt;Now that I have an access point that my phone can connect to, it's time to start actually polling with the DNS server. Remember that the access point configuration is telling all its client that it is the DNS server, so when my phone connects and tries to look up a domain name, it will be asking the MCU itself what IP address that URL points to.&lt;/p&gt;

&lt;p&gt;Make the following additions to the &lt;code&gt;CaptivePortal&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# captive_portal.py
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;gc&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;uselect&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;captive_dns&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DNSServer&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CaptivePortal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;AP_IP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"192.168.4.1"&lt;/span&gt;
    &lt;span class="n"&gt;CRED_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"./wifi.creds"&lt;/span&gt;
    &lt;span class="n"&gt;MAX_CONN_ATTEMPTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;essid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dns_server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;captive_portal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting captive portal"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_access_point&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dns_server&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dns_server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DNSServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Configured DNS server"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="c1"&gt;# check for socket events and handle them
&lt;/span&gt;                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ipoll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;others&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
                    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_dns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Captive portal stopped"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_dns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dns_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# ignore UDP socket hangups
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POLLHUP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dns_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Cleaning up"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dns_server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dns_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poller&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here I'm using the &lt;code&gt;uselect&lt;/code&gt; module to create a &lt;code&gt;Poll&lt;/code&gt; object. When I instantiate the DNSServer class, it registers its socket with the poller, and any events on that socket stream will show up as a response that I can iterate through. I'm calling the &lt;code&gt;ipoll&lt;/code&gt; method with a timeout of 1000msec, and if there's any event, I check whether the DNSServer wants to handle it.&lt;/p&gt;

&lt;p&gt;Since UDP is a connectionless protocol (and possibly due to a bug in the ESP8266 port of MicroPython), I found I was getting dozens of &lt;code&gt;POLLHUP&lt;/code&gt; (stream hang-up) events per second after the first, which are useless to me in this application. I ignore those for now, and otherwise send the event to the DNSServer to handle itself.&lt;/p&gt;

&lt;p&gt;To test it all out, I saved, copied, and ran from REPL again. After I connected my phone to the access point, I can see some DNS requests coming in. After confirming it worked, I used &lt;code&gt;Ctrl-C&lt;/code&gt; to exit, and verified the cleanup code was also called.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;MicroPython v1.12 on 2019-12-20&lt;span class="p"&gt;;&lt;/span&gt; ESP module with ESP8266
Type &lt;span class="s2"&gt;"help()"&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;more information.
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; import main
Trying to load WiFi credentials from ./wifi.creds
./wifi.creds does not exist
Starting captive portal
Waiting &lt;span class="k"&gt;for &lt;/span&gt;access point to turn on
&lt;span class="c"&gt;#50 ets_task(4020f510, 29, 3fff9428, 10)&lt;/span&gt;
AP mode configured: &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'192.168.4.1'&lt;/span&gt;, &lt;span class="s1"&gt;'255.255.255.0'&lt;/span&gt;, &lt;span class="s1"&gt;'192.168.4.1'&lt;/span&gt;, &lt;span class="s1"&gt;'192.168.4.1'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
DNS Server listening on &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0.0.0.0'&lt;/span&gt;, 53&lt;span class="o"&gt;)&lt;/span&gt;
Configured DNS server
Got data: b&lt;span class="s1"&gt;'\xa7A\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x06google\x03com
\x00\x00\x01\x00\x01'&lt;/span&gt; from sender: &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'192.168.4.2'&lt;/span&gt;, 42434&lt;span class="o"&gt;)&lt;/span&gt;
Got data: b&lt;span class="s1"&gt;'Eo\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x11connectivitycheck\x07gs
tatic\x03com\x00\x00\x01\x00\x01'&lt;/span&gt; from sender: &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'192.168.4.2'&lt;/span&gt;, 13197&lt;span class="o"&gt;)&lt;/span&gt;
Got data: b&lt;span class="s1"&gt;'\xa7A\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x06google\x03com
\x00\x00\x01\x00\x01'&lt;/span&gt; from sender: &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'192.168.4.2'&lt;/span&gt;, 42434&lt;span class="o"&gt;)&lt;/span&gt;
Got data: b&lt;span class="s1"&gt;'Eo\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x11connectivitycheck\x07gs
tatic\x03com\x00\x00\x01\x00\x01'&lt;/span&gt; from sender: &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'192.168.4.2'&lt;/span&gt;, 13197&lt;span class="o"&gt;)&lt;/span&gt;
Captive portal stopped
Cleaning up
DNS Server stopped
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Recap
&lt;/h1&gt;

&lt;p&gt;So far, I've accomplished the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configured the Wemos D1 Mini MCU with MicroPython firmware and established a serial connection.&lt;/li&gt;
&lt;li&gt;Made the MCU try to connect to an already-known WiFi point if it has one, on bootup.&lt;/li&gt;
&lt;li&gt;Written a base server class that can bind to a socket and register to receive events on that socket stream.&lt;/li&gt;
&lt;li&gt;Written the skeleton of a DNS server that listens to UDP datagrams on port 53 and displays them on screen.&lt;/li&gt;
&lt;li&gt;Set up the basic event loop of the captive server to poll for events and handle them appropriately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next part, I'll tackle how to understand DNS questions and generate an answer that will point all DNS queries back to the MCU's IP address so that the user will always be presented with a login page when they connect to the MCU's WiFi access point.&lt;/p&gt;




&lt;p&gt;Note: if you purchase an item from Amazon using any of the links in this article, I may receive a small commission.&lt;/p&gt;

</description>
      <category>micropython</category>
      <category>python</category>
      <category>dns</category>
      <category>esp8266</category>
    </item>
    <item>
      <title>HMAC signatures for Insomnia requests</title>
      <dc:creator>Anson VanDoren</dc:creator>
      <pubDate>Fri, 27 Dec 2019 21:44:08 +0000</pubDate>
      <link>https://dev.to/ansonvandoren/hmac-signatures-for-insomnia-requests-1h9h</link>
      <guid>https://dev.to/ansonvandoren/hmac-signatures-for-insomnia-requests-1h9h</guid>
      <description>&lt;p&gt;I've been working with the &lt;a href="https://github.com/binance-exchange/binance-official-api-docs"&gt;Binance API&lt;/a&gt; recently, and found I was spending too much time making manual &lt;a href="https://curl.haxx.se/"&gt;curl&lt;/a&gt; or &lt;a href="http://docs.python-requests.org/en/master/"&gt;requests&lt;/a&gt; calls just to see what sort of data I was getting back in order to appropriately process it. It never occurred to me that there might be a better way until I stumbled across the &lt;a href="https://insomnia.rest/"&gt;Insomnia&lt;/a&gt; REST client last night.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with APIs with Insomnia
&lt;/h3&gt;

&lt;p&gt;If you haven't worked with Insomnia before, but you do spend a decent amount of time either producing or consuming APIs, go check it out now. If you've used Postman, it's probably not going to blow your socks off, but if you've ever found yourself trying to remember all the curl options just to make a simple request and check out the response, you'll probably love this.&lt;/p&gt;

&lt;p&gt;A few minutes after installing it, I had this set up for my Binance work:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UdGy2OLM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/anson-vandoren/ansonvandoren.com/raw/master/static/images/insomnia_client.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UdGy2OLM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/anson-vandoren/ansonvandoren.com/raw/master/static/images/insomnia_client.png" alt="Insomnia REST client screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I added a few environment variables that work across all calls (base API URL, API key, API secret, etc.), and then created new endpoints for each of the API calls I need to refer to. I won't go into much more detail here since it's pretty easy to set up and use, but if this looks interesting for your work, definately go check it out!&lt;/p&gt;
&lt;h3&gt;
  
  
  Signed requests
&lt;/h3&gt;

&lt;p&gt;This was all well and good until I got the part that I really wanted to do, which was executing API operations like listing account balances and placing trades. Binance uses &lt;a href="https://blog.andrewhoang.me/how-api-request-signing-works-and-how-to-implement-it-in-nodejs-2/"&gt;HMAC signing&lt;/a&gt; to confirm sensitive requests come from authorized individuals only. The &lt;a href="https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#signed-trade-and-user_data-endpoint-security"&gt;Binance API docs&lt;/a&gt; have a reasonable amount of information on how to do this, but in my actual code I'm using the &lt;a href="https://github.com/sammchardy/python-binance"&gt;python-binance&lt;/a&gt; library to take care of it so I hadn't looked into it much.&lt;/p&gt;

&lt;p&gt;Insomnia comes with a few &lt;a href="https://support.insomnia.rest/article/38-authentication"&gt;auth options&lt;/a&gt; out-of-the-box, but none of them worked for my case. There are a handful of &lt;a href="https://www.npmjs.com/search?q=insomnia-plugin"&gt;Insomnia plugins&lt;/a&gt; available on the NPM registry, but none that worked for what I needed.&lt;/p&gt;
&lt;h3&gt;
  
  
  Insomnia plugins
&lt;/h3&gt;

&lt;p&gt;That brings me to the real reason for writing this post, which is so that future-me will remember how I solved this and not re-invent the wheel when I run across this again down the road. The Insomnia docs have a page on &lt;a href="https://support.insomnia.rest/article/26-plugins"&gt;writing plugins&lt;/a&gt;, but it's not as well documented as it could be.&lt;/p&gt;

&lt;p&gt;Basically I had two choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A template tag, which I could reference like an environment variable inside of the client&lt;/li&gt;
&lt;li&gt;A request/response hook that is triggered either just before the request is sent out, or upon receiving a response to a previous request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My first thought was to write a template tag that I could just put into the signature query parameter which would look at the other parameters, compute the HMAC, and write it out before sending. I would still like to implement it this way, but I ran into a problem relating to how the timestamp tag (an Insomnia built-in) was updating after I computed the hash but before sending the request, which rendered the signature invalid before it was sent off.&lt;/p&gt;

&lt;p&gt;Since this didn't seem to be an easy option, I chose instead to write a request hook that looks at all requests, checks whether they are going to Binance, and if so, whether they need to be signed. In the particular case of Binance, I chose to make this second part trigger off whether there was a &lt;code&gt;timestamp&lt;/code&gt; parameter already included in the query. All of the Binance REST endpoints that need to be signed also require a timestamp, and the other endpoints do not accept one.&lt;/p&gt;

&lt;p&gt;The basic structure of my request hook looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// insomnia_plugin_binance_signing.js&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestHooks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Validate context object&lt;/span&gt;
        &lt;span class="c1"&gt;// Check the URL points to the Binance API&lt;/span&gt;
        &lt;span class="c1"&gt;// Check if a timestamp parameter exists&lt;/span&gt;
        &lt;span class="c1"&gt;// Make sure there is not already a signature parameter&lt;/span&gt;
        &lt;span class="c1"&gt;// Check the API Key is present in the environment variables&lt;/span&gt;
        &lt;span class="c1"&gt;// Set a time window to prevent time-sensitive request from executing late&lt;/span&gt;
        &lt;span class="c1"&gt;// Compose the query string from the request&lt;/span&gt;
        &lt;span class="c1"&gt;// Generate the signature&lt;/span&gt;
        &lt;span class="c1"&gt;// Set the signature&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The first couple of validations are boring, so I won't include them here, but the whole thing &lt;a href="https://github.com/anson-vandoren/insomnia-plugin-binance-signing"&gt;is on GitHub&lt;/a&gt; if you're curious. Basically I'm just ensuring the context object exists, it has a &lt;code&gt;request&lt;/code&gt; property, and that request property has a &lt;code&gt;getUrl()&lt;/code&gt; method. If any check fails, just return early and do nothing.&lt;/p&gt;

&lt;p&gt;Below is the basic implementation, skipping redundant parts. Again, check out the full code if you want more detail.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;        &lt;span class="c1"&gt;// Validate context object&lt;/span&gt;
        &lt;span class="c1"&gt;// ... boring stuff...&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Check the URL points to the Binance API&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getUrl&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.binance.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not a Binance API URL.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Check if a timestamp parameter exists&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timestamp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No timestamp parameter, not signing.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Check the API Key is present in the environment variables&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api_secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cannot find environment variable 'api_secret'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Generating request signature...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// The message to be signed for Binance API is the query string&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;computeSigningBase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Use crypto-js library to compute the hash&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;computeHttpSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Set the signature parameter on the outgoing request&lt;/span&gt;
        &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Signature appended to outgoing request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The context object doesn't provide the query string directly, but it can be generated easily:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;computeSigningBase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paramObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getParameters&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;paramObj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The hash function is straightforward from the crypto-js library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CryptoJS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;encodeURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\+&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;computeHttpSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CryptoJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HmacSHA256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;encodeUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CryptoJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Hex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Using the plugin
&lt;/h3&gt;

&lt;p&gt;Once I was happy with how things worked, I wrote up a basic &lt;code&gt;package.json&lt;/code&gt; file and published to the NPM registry as &lt;code&gt;insomnia_plugin_binance_signing&lt;/code&gt;. Insomnia has a plugin manager that will search NPM packages and automatically install from there. Once it loads the plugin (for a response/request hook), it will automatically apply that plugin to all incoming/outgoing messages, so there's nothing special I need to do in my setup after this.&lt;/p&gt;

&lt;p&gt;Had I went with a template tag, the only additional step would have been to add the tag into the correct spot in the request GUI.&lt;/p&gt;

&lt;p&gt;I don't have the rest of the signed endpoints set up yet, but the ones I have tried work perfectly now. If any request includes the timestamp parameter already (using the Insomnia built-in), it will be signed on its way out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gbDcsHsd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/anson-vandoren/ansonvandoren.com/raw/master/static/images/insomnia_signed_request.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gbDcsHsd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/anson-vandoren/ansonvandoren.com/raw/master/static/images/insomnia_signed_request.png" alt="Insomnia REST client screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>hmac</category>
      <category>insomnia</category>
      <category>binance</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Telegram notifications from website deployment</title>
      <dc:creator>Anson VanDoren</dc:creator>
      <pubDate>Tue, 24 Dec 2019 14:53:19 +0000</pubDate>
      <link>https://dev.to/ansonvandoren/telegram-notifications-from-website-deployment-3op2</link>
      <guid>https://dev.to/ansonvandoren/telegram-notifications-from-website-deployment-3op2</guid>
      <description>&lt;p&gt;I recently &lt;a href="https://dev.to/ansonvandoren/deploying-a-hugo-site-on-a-github-commit-53ld"&gt;set up webhooks&lt;/a&gt; on my remote machine to receive POST requests from GitHub whenever I pushed a change to &lt;a href="https://ansonvandoren.com"&gt;ansonvandoren.com&lt;/a&gt;. It works well, but since it's running as a background service on a remote server, I don't get a lot of detail about its status.&lt;/p&gt;

&lt;p&gt;About a year ago, when I first started using &lt;a href="https://telegram.org"&gt;Telegram&lt;/a&gt; as my daily-driver chat app (thanks, Google, for cancelling or ruining every chat service you've ever invented), I noticed they also offered an API. I immediately thought of several things that could be useful for, but lacked the time and motivation to explore it further.&lt;/p&gt;

&lt;p&gt;Today I had both a good project, and some time to implement it, so I started fiddling around.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I want to accomplish:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Current process&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make website changes locally, commit them to a git repo, and push to GitHub.&lt;/li&gt;
&lt;li&gt;GitHub sends a POST request to my server, which then:

&lt;ul&gt;
&lt;li&gt;Clones the repo locally on the server.&lt;/li&gt;
&lt;li&gt;Builds the static site using Hugo.&lt;/li&gt;
&lt;li&gt;Copies the built content to the public WWW folder.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If any part of 2. fails, it will roll back to the previous version and log a simple failure message.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What I want to add&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Integrate with Telegram so that I will get a notification when a build/deploy succeeds, and a failure message otherwise with some indication of what went wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where I'm starting from
&lt;/h3&gt;

&lt;p&gt;Here's my current build script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash -e&lt;/span&gt;
&lt;span class="c"&gt;# Note the '-e' in the line above. This is required for the&lt;/span&gt;
&lt;span class="c"&gt;# error trapping implemented below.&lt;/span&gt;

&lt;span class="c"&gt;# Repo name on GitHub&lt;/span&gt;
&lt;span class="nv"&gt;REMOTE_REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://github.com/anson-vandoren/ansonvandoren.com.git
&lt;span class="c"&gt;# A place to clone the remote repo so Hugo can build from it&lt;/span&gt;
&lt;span class="nv"&gt;WORKING_DIRECTORY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/staging_area
&lt;span class="c"&gt;# Location (server block) where Nginx looks for content to serve&lt;/span&gt;
&lt;span class="nv"&gt;PUBLIC_WWW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/www/ansonvandoren.com/html
&lt;span class="c"&gt;# Backup folder in case something goes wrong during this script&lt;/span&gt;
&lt;span class="nv"&gt;BACKUP_WWW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/backup_html
&lt;span class="c"&gt;# Domain name so Hugo can generate links correctly&lt;/span&gt;
&lt;span class="nv"&gt;MY_DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ansonvandoren.com

&lt;span class="c"&gt;# For future notifications on Telegram&lt;/span&gt;
&lt;span class="nv"&gt;commit_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;pusher_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
&lt;span class="nv"&gt;commit_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;

&lt;span class="c"&gt;# If something goes wrong, put the previous version back in place&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;cleanup &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"A problem occurred. Reverting to backup."&lt;/span&gt;
    rsync &lt;span class="nt"&gt;-aqz&lt;/span&gt; &lt;span class="nt"&gt;--del&lt;/span&gt; &lt;span class="nv"&gt;$BACKUP_WWW&lt;/span&gt;/ &lt;span class="nv"&gt;$PUBLIC_WWW&lt;/span&gt;
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$WORKING_DIRECTORY&lt;/span&gt;
    &lt;span class="c"&gt;# **Placeholder for Telegram notification**&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Call the cleanup function if this script exits abnormally. The -e flag&lt;/span&gt;
&lt;span class="c"&gt;# in the shebang line ensures an immediate abnormal exit on any error&lt;/span&gt;
&lt;span class="nb"&gt;trap &lt;/span&gt;cleanup EXIT

&lt;span class="c"&gt;# Clear out the working directory&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$WORKING_DIRECTORY&lt;/span&gt;
&lt;span class="c"&gt;# Make a backup copy of current website version&lt;/span&gt;
rsync &lt;span class="nt"&gt;-aqz&lt;/span&gt; &lt;span class="nv"&gt;$PUBLIC_WWW&lt;/span&gt;/ &lt;span class="nv"&gt;$BACKUP_WWW&lt;/span&gt;

&lt;span class="c"&gt;# Clone the new version from GitHub&lt;/span&gt;
git clone &lt;span class="nv"&gt;$REMOTE_REPO&lt;/span&gt; &lt;span class="nv"&gt;$WORKING_DIRECTORY&lt;/span&gt;

&lt;span class="c"&gt;# **Placeholder for Telegram notification**&lt;/span&gt;

&lt;span class="c"&gt;# Delete the old version&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$PUBLIC_WWW&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="c"&gt;# Have Hugo generate the new static HTML directly into the public WWW folder&lt;/span&gt;
/usr/local/bin/hugo &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$WORKING_DIRECTORY&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;$PUBLIC_WWW&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MY_DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# **Placeholder for Telegram notification**&lt;/span&gt;

&lt;span class="c"&gt;# Clear out working directory&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$WORKING_DIRECTORY&lt;/span&gt;
&lt;span class="c"&gt;# Exit without trapping, since everything went well&lt;/span&gt;
&lt;span class="nb"&gt;trap&lt;/span&gt; - EXIT
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can see where I stubbed in the places I thought I could hook into Telegram, but so far there's no code or infrastructure behind it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Telegram bot
&lt;/h3&gt;

&lt;p&gt;After some initial research on the &lt;a href="https://core.telegram.org/bots/api"&gt;Telegram API documentation&lt;/a&gt;, the first step seems to be to create a new bot using the "&lt;a href="https://telegram.me/botfather"&gt;BotFather&lt;/a&gt;". This is a painless process done entirely through the Telegram app, and within a few seconds I was the proud owner of &lt;a href="https://t.me/ansonvandoren_bot"&gt;ansonvandoren_bot&lt;/a&gt;, and I had my API token (hereafter denoted &lt;code&gt;&amp;lt;TOKEN&amp;gt;&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;After obtaining the token, I started a chat with the bot so I could get a conversation ID. I don't think it's possible for the bot to initiate a chat (probably a good thing), so I need to take the first step. The BotFather conversation will have a link to start talking with your robotic spawn, or else you can just go to &lt;a href="https://t.me/your_new_bot"&gt;https://t.me/your_new_bot&lt;/a&gt; to get redirected.&lt;/p&gt;

&lt;p&gt;Getting the chat ID can be done from a browser, or from the command line via a tool like &lt;code&gt;curl&lt;/code&gt;. If you're not familiar with the curl tool, there is a &lt;a href="https://ec.haxx.se/"&gt;very readable guide here&lt;/a&gt;. In this first case, I'm using it with no arguments, which defaults to sending a HTTP GET request to the URL specified, and displaying the output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl https://api.telegram.org/bot&amp;lt;TOKEN&amp;gt;/getUpdates
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;:true,&lt;span class="s2"&gt;"result"&lt;/span&gt;:[&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"update_id"&lt;/span&gt;:424724792,
...&lt;span class="s2"&gt;"chat"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:123456184,&lt;span class="s2"&gt;"first_name"&lt;/span&gt;:&lt;span class="s2"&gt;"Anson"&lt;/span&gt;,&lt;span class="s2"&gt;"last_name"&lt;/span&gt;:&lt;span class="s2"&gt;"VanDoren"&lt;/span&gt;,...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;"id":123456184&lt;/code&gt; field here is what I was looking for, and is the identifier for my conversation with the bot. Since my particular use-case only involves a single conversation (me vs. The Bot), I don't need to get any fancier than this. I'll refer to this field as &lt;code&gt;&amp;lt;CHAT_ID&amp;gt;&lt;/code&gt; for the rest of this post.&lt;/p&gt;

&lt;p&gt;Now that I've got the two key pieces of information, I can try sending a message. I'm using &lt;code&gt;curl&lt;/code&gt; again, but this time with the &lt;code&gt;-s&lt;/code&gt; (silent) and &lt;code&gt;-X POST&lt;/code&gt; use the HTTP POST verb instead of GET. I'm specifying the parameters I want to send with the &lt;code&gt;-d&lt;/code&gt; options, one at a time. Scroll right to see the whole thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.telegram.org/bot&amp;lt;TOKEN&amp;gt;/sendMessage &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;chat_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;CHAT_ID&amp;gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"The bot speaks!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ls8N5Ks---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/telegram-bot-test-message.jpg%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ls8N5Ks---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/telegram-bot-test-message.jpg%23center" alt="Sending a test message"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It worked! That should really be most of the API functionality I'll need for this little project. The API is actually very robust, and geared toward richer content and interactivity. I will probably come back and explore it later, but this project has a pretty narrow scope and I just wanted to get it finished up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrating the bot with my build/deploy script
&lt;/h3&gt;

&lt;p&gt;All that's left is to figure out how to work this into my build script. This part can be as basic or complicated as you want. Below is how I decided to get notified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash -e&lt;/span&gt;
&lt;span class="c"&gt;# Note the '-e' in the line above. This is required for&lt;/span&gt;
&lt;span class="c"&gt;# error trapping implemented below.&lt;/span&gt;

&lt;span class="c"&gt;# Repo name on GitHub&lt;/span&gt;
&lt;span class="nv"&gt;REMOTE_REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://github.com/anson-vandoren/ansonvandoren.com.git
&lt;span class="c"&gt;# A place to clone the remote repo so Hugo can build from it&lt;/span&gt;
&lt;span class="nv"&gt;WORKING_DIRECTORY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/staging_area
&lt;span class="c"&gt;# Location (server block) where Nginx looks for content to serve&lt;/span&gt;
&lt;span class="nv"&gt;PUBLIC_WWW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/www/ansonvandoren.com/html
&lt;span class="c"&gt;# Backup folder in case something goes wrong during this script&lt;/span&gt;
&lt;span class="nv"&gt;BACKUP_WWW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/backup_html
&lt;span class="c"&gt;# Domain name so Hugo can generate links correctly&lt;/span&gt;
&lt;span class="nv"&gt;MY_DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ansonvandoren.com

&lt;span class="c"&gt;# Set up Telegram&lt;/span&gt;
&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;INSERT_YOUR_TOKEN_HERE
&lt;span class="nv"&gt;CHAT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;INSERT_YOUR_CHAT_ID_HERE
&lt;span class="nv"&gt;BOT_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://api.telegram.org/bot&lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;/sendMessage"&lt;/span&gt;

&lt;span class="c"&gt;# Send messages to Telegram bot&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;send_msg &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;# Use "$1" to get the first argument (desired message) passed to this function&lt;/span&gt;
    &lt;span class="c"&gt;# Set parsing mode to HTML because Markdown tags don't play nice in a bash script&lt;/span&gt;
    &lt;span class="c"&gt;# Redirect curl output to /dev/null since we don't need to see it&lt;/span&gt;
    &lt;span class="c"&gt;# (it just replays the message from the bot API)&lt;/span&gt;
    &lt;span class="c"&gt;# Redirect stderr to stdout so we can still see an error message in curl if it occurs&lt;/span&gt;
    curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nv"&gt;$BOT_URL&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;chat_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$CHAT_ID&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;parse_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"HTML"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;commit_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;pusher_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
&lt;span class="nv"&gt;commit_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;

&lt;span class="c"&gt;# If something goes wrong, put the previous verison back in place&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;cleanup &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"A problem occurred. Reverting to backup."&lt;/span&gt;
    rsync &lt;span class="nt"&gt;-aqz&lt;/span&gt; &lt;span class="nt"&gt;--del&lt;/span&gt; &lt;span class="nv"&gt;$BACKUP_WWW&lt;/span&gt;/ &lt;span class="nv"&gt;$PUBLIC_WWW&lt;/span&gt;
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$WORKING_DIRECTORY&lt;/span&gt;

    &lt;span class="c"&gt;# Use $? to get the error message that caused the failure&lt;/span&gt;
    send_msg &lt;span class="s2"&gt;"&amp;lt;b&amp;gt;Deployment of ansonvandoren.com failed:&amp;lt;/b&amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Call the cleanup function if this script exits abnormally. The -e flag&lt;/span&gt;
&lt;span class="c"&gt;# in the shebang line ensures an immediate abnormal exit on any error&lt;/span&gt;
&lt;span class="nb"&gt;trap &lt;/span&gt;cleanup EXIT

&lt;span class="c"&gt;# Clear out the working directory&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$WORKING_DIRECTORY&lt;/span&gt;
&lt;span class="c"&gt;# Make a backup copy of current website version&lt;/span&gt;
rsync &lt;span class="nt"&gt;-aqz&lt;/span&gt; &lt;span class="nv"&gt;$PUBLIC_WWW&lt;/span&gt;/ &lt;span class="nv"&gt;$BACKUP_WWW&lt;/span&gt;

&lt;span class="c"&gt;# Clone the new version from GitHub&lt;/span&gt;
git clone &lt;span class="nv"&gt;$REMOTE_REPO&lt;/span&gt; &lt;span class="nv"&gt;$WORKING_DIRECTORY&lt;/span&gt;

send_msg &lt;span class="s2"&gt;"&amp;lt;i&amp;gt;Successfully cloned GitHub repo for ansonvandoren.com&amp;lt;/i&amp;gt;
&amp;lt;code&amp;gt;Message: &lt;/span&gt;&lt;span class="nv"&gt;$commit_message&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/code&amp;gt;
&amp;lt;code&amp;gt;Pushed by: &lt;/span&gt;&lt;span class="nv"&gt;$pusher_name&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/code&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Delete old version&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$PUBLIC_WWW&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="c"&gt;# Have Hugo generate the new static HTML directly into the public WWW folder&lt;/span&gt;
&lt;span class="c"&gt;# Save the output of Hugo to send to Telegram&lt;/span&gt;
&lt;span class="nv"&gt;hugo_response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;/usr/local/bin/hugo &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$WORKING_DIRECTORY&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;$PUBLIC_WWW&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MY_DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# Send Hugo response to bot as a fenced code block to preserve formatting&lt;/span&gt;
send_msg &lt;span class="s2"&gt;"&amp;lt;pre&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;$hugo_response&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/pre&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;# All done!&lt;/span&gt;
send_msg &lt;span class="s2"&gt;"&amp;lt;b&amp;gt;Deployment successful!&amp;lt;/b&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Clear out working directory&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$WORKING_DIRECTORY&lt;/span&gt;
&lt;span class="c"&gt;# Exit without trapping, since everything went well&lt;/span&gt;
&lt;span class="nb"&gt;trap&lt;/span&gt; - EXIT
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A few things I learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to pass arguments to a bash function: this is different from 'normal' progrmaming languages in that you don't define what the arguments are, but simply refer to them in sequential order with $1, $2, etc. within the function.&lt;/li&gt;
&lt;li&gt;How to hide the output of a command: &lt;code&gt;curl... &amp;gt; /dev/null&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;How to redirect error output to stdout: &lt;code&gt;2&amp;gt;&amp;amp;1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;How to get the last error message raised by the script: &lt;code&gt;$?&lt;/code&gt;
&lt;em&gt;Note: I have not been able to test this yet because I've not seen any errors on deployment. StackOverflow seems pretty convinced it should work, though.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://core.telegram.org/bots/api#formatting-options"&gt;limited subset of HTML&lt;/a&gt; can be used to format Telegram bot messages. Markdown (an alternative formatting option allowed by the API) does not work well inside a bash script because the back-ticks mess up the formatting of the script.&lt;/li&gt;
&lt;li&gt;You can save the output of a command to a variable using &lt;code&gt;variable_name=$(command)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;To use the full (multi-line) response stored from a command output as above, you need to put it inside double quotes. &lt;code&gt;$hugo_response&lt;/code&gt; in my example would only show the first line, but &lt;code&gt;"$hugo_response"&lt;/code&gt; does multi-line.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing it all out
&lt;/h3&gt;

&lt;p&gt;As with previous examples, I created a dummy commit and pushed to GitHub to try it out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;--allow-empty&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Test commit"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git push
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--18Y0jkRd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/telegram-bot-deploy-message.jpg%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--18Y0jkRd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ansonvandoren.com/images/telegram-bot-deploy-message.jpg%23center" alt="Getting a commit notification"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>bash</category>
      <category>telegram</category>
      <category>github</category>
    </item>
    <item>
      <title>Configuring Nginx to Proxy Webhooks</title>
      <dc:creator>Anson VanDoren</dc:creator>
      <pubDate>Mon, 23 Dec 2019 14:21:03 +0000</pubDate>
      <link>https://dev.to/ansonvandoren/configuring-nginx-to-proxy-webhooks-12hc</link>
      <guid>https://dev.to/ansonvandoren/configuring-nginx-to-proxy-webhooks-12hc</guid>
      <description>&lt;p&gt;In my &lt;a href="https://dev.to/ansonvandoren/deploying-a-hugo-site-on-a-github-commit-53ld"&gt;last post&lt;/a&gt;, I set up a workflow that allows me to make changes to my website in a local git repository, push it &lt;a href="https://github.com/anson-vandoren/ansonvandoren.com.git"&gt;to GitHub&lt;/a&gt;, and have my remote server build the static content with Hugo and serve up the changes. On the remote server, I handled the GitHub hooks through a small Golang server called &lt;code&gt;webhook&lt;/code&gt;, but wasn't really happy with needing to open up a separate port (it defaults to 9000) on my server firewall. In the grand scheme of things, it probably doesn't matter much from a security standpoint, but I thought I could do a bit better and learn something in the process of configuring Nginx to proxy the requests through HTTPS.&lt;/p&gt;

&lt;p&gt;When I set up my website, I had already used &lt;a href="https://letsencrypt.org/"&gt;Let's Encrypt&lt;/a&gt; to enable HTTPS by default. As part of that process, I used the &lt;code&gt;certbot&lt;/code&gt; tool they provide to automatically modify my Nginx server block configuration, but now I needed to go back and modify them by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting configuration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Firewall status&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At the end of the last post, I had port 9000 open through ufw to enable the webhooks server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status
Status: active

To                         Action      From
&lt;span class="nt"&gt;--&lt;/span&gt;                         &lt;span class="nt"&gt;------&lt;/span&gt;      &lt;span class="nt"&gt;----&lt;/span&gt;
OpenSSH                    ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere
9000                       ALLOW       Anywhere
OpenSSH &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;               ALLOW       Anywhere &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;
Nginx Full &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;            ALLOW       Anywhere &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;
9000 &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;                  ALLOW       Anywhere &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Nginx configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My starting Nginx configuration looked like this, based on &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-server-blocks-virtual-hosts-on-ubuntu-16-04"&gt;these instructions&lt;/a&gt; and what Certbot changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;server &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;# Redirect www to non-www. Hugo doesn't like subdomains&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; www.ansonvandoren.com&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;301 https://ansonvandoren.com&lt;span class="nv"&gt;$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    root /var/www/ansonvandoren.com/html&lt;span class="p"&gt;;&lt;/span&gt;
    index index.html index.htm index.nginx-debian.html&lt;span class="p"&gt;;&lt;/span&gt;
    server_name www.ansonvandoren.com ansonvandoren.com&lt;span class="p"&gt;;&lt;/span&gt;

    location / &lt;span class="o"&gt;{&lt;/span&gt;
        try_files &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;/ &lt;span class="o"&gt;=&lt;/span&gt;404&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    listen &lt;span class="o"&gt;[&lt;/span&gt;::]:443 ssl &lt;span class="nv"&gt;ipv6only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;# managed by Certbot&lt;/span&gt;
    listen 443 ssl&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;# managed by Certbot&lt;/span&gt;
    ssl_certificate /etc/letsencrypt/live/ansonvandoren.com/fullchain.pem&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;# managed by Certbot&lt;/span&gt;
    ssl_certificate_key /etc/letsencrypt/live/ansonvandoren.com/privkey.pem&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;# managed by Certbot&lt;/span&gt;
    include /etc/letsencrypt/options-ssl-nginx.conf&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;# managed by Certbot&lt;/span&gt;
    ssl dhparam /etc/letsencrypt/ssl-dhparams.pem&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;# managed by Certbot&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
server &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;# Redirect to HTTPS (and also redirect www to non-www)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; www.ansonvandoren.com&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;301 https://ansonvandoren.com&lt;span class="nv"&gt;$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="c"&gt;# managed by Certbot&lt;/span&gt;

    &lt;span class="c"&gt;# Redirect to HTTPS&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; ansonvandoren.com&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;301 https://&lt;span class="nv"&gt;$host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="c"&gt;# managed by Certbot&lt;/span&gt;

    listen 80&lt;span class="p"&gt;;&lt;/span&gt;
    listen &lt;span class="o"&gt;[&lt;/span&gt;::]:80&lt;span class="p"&gt;;&lt;/span&gt;
    server_name ansonvandoren.com www.ansonvandoren.com

    &lt;span class="k"&gt;return &lt;/span&gt;404&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;# managed by Certbot&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Actually, it was quite a bit messier than that when I first looked, but I did some cleanup and rearranging through trial and error until I got things looking the way I wanted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;webhooks server&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;webhooks&lt;/code&gt; server was listening on 0.0.0.0, port 9000&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My website repo on GitHub had a webhook set up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payload URL: &lt;a href="http://ansonvandoren.com:9000/hooks/redeploy"&gt;http://ansonvandoren.com:9000/hooks/redeploy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Content type: application/json&lt;/li&gt;
&lt;li&gt;Enable SSL verification turned on&lt;/li&gt;
&lt;li&gt;Triggering event set to 'Just the push event'&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I wanted instead
&lt;/h2&gt;

&lt;p&gt;I had two main things I wanted to accomplish with these changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Close port 9000 on &lt;code&gt;ufw&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Change my &lt;strong&gt;Payload URL&lt;/strong&gt; on GitHub to use HTTPS (and not require port 9000)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How I did it
&lt;/h2&gt;

&lt;p&gt;In hindsight, this was actually not a difficult problem to solve, but it did take quite a bit of trial and error, and I couldn't find any examples that accomplished exactly what I was looking for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Close the firewall port&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since I'm the only one pushing commits to the website repo, I won't be interrupting anything if I close the port now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw delete allow 9000
Rule deleted
Rule deleted &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status
Status: active

To                         Action      From
&lt;span class="nt"&gt;--&lt;/span&gt;                         &lt;span class="nt"&gt;------&lt;/span&gt;      &lt;span class="nt"&gt;----&lt;/span&gt;
OpenSSH                    ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere
OpenSSH &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;               ALLOW       Anywhere &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;
Nginx Full &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;            ALLOW       Anywhere &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So far, so good...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add Nginx proxy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This took me the most time to sort out, but it's relatively simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/nginx/sites-available/ansonvandoren.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In my first server block (the one listening on 443), I added the lines shown below for the &lt;code&gt;/hooks/&lt;/code&gt; location without changing anything else in the server blocks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;server &lt;span class="o"&gt;{&lt;/span&gt;
    ...
    server_name www.ansonvandoren.com ansonvandoren.com&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c"&gt;# Forward webhook requests&lt;/span&gt;
    location /hooks/ &lt;span class="o"&gt;{&lt;/span&gt;
        proxy_pass http://127.0.0.1:9000/hooks/&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    location / &lt;span class="o"&gt;{&lt;/span&gt;
        try_files &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;/ &lt;span class="o"&gt;=&lt;/span&gt;404&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    ...
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Simple, right? If any web requests come in over HTTPS on the /hooks path, they get proxied to localhost:9000, which is where the webhooks server is already listening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update GitHub&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Almost done, and just need to let my GitHub webhook know about the changes. Back on the same settings page, I just needed to change the Payload URL to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payload URL: &lt;a href="https://ansonvandoren.com/hooks/redeploy"&gt;https://ansonvandoren.com/hooks/redeploy&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To prevent you needing to scroll back up, I just removed the ':9000' port identifier, and changed the http:// to https://&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing it out
&lt;/h3&gt;

&lt;p&gt;To make sure everything worked after my changes, I made some edits to my site locally, committed them, and pushed to GitHub.&lt;/p&gt;

&lt;p&gt;The webhook service looks good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; status webhook
● webhook.service - Simple Golang webhook server
   Loaded: loaded &lt;span class="o"&gt;(&lt;/span&gt;/home/bloguser/.config/systemd/user/webhook.service&lt;span class="p"&gt;;&lt;/span&gt; enabled&lt;span class="p"&gt;;&lt;/span&gt; vendor preset: enabled&lt;span class="o"&gt;)&lt;/span&gt;
   Active: active &lt;span class="o"&gt;(&lt;/span&gt;running&lt;span class="o"&gt;)&lt;/span&gt; since Mon 2019-01-21 00:40:37 UTC&lt;span class="p"&gt;;&lt;/span&gt; 1h 40min ago
 Main PID: 851 &lt;span class="o"&gt;(&lt;/span&gt;webhook&lt;span class="o"&gt;)&lt;/span&gt;
   CGroup: /user.slice/user-1000.slice/user@1000.service/webhook.service
           └─851 /usr/local/bin/webhook &lt;span class="nt"&gt;-ip&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;-hooks&lt;/span&gt; /opt/hooks/hooks.json &lt;span class="nt"&gt;-verbose&lt;/span&gt;

Jan 21 01:22:00 ansonvandoren webhook[851]:   Pages            | 20
Jan 21 01:22:00 ansonvandoren webhook[851]:   Paginator pages  |  0
Jan 21 01:22:00 ansonvandoren webhook[851]:   Non-page files   |  0
Jan 21 01:22:00 ansonvandoren webhook[851]:   Static files     |  5
Jan 21 01:22:00 ansonvandoren webhook[851]:   Processed images |  0
Jan 21 01:22:00 ansonvandoren webhook[851]:   Aliases          |  7
Jan 21 01:22:00 ansonvandoren webhook[851]:   Sitemaps         |  1
Jan 21 01:22:00 ansonvandoren webhook[851]:   Cleaned          |  0
Jan 21 01:22:00 ansonvandoren webhook[851]: Total &lt;span class="k"&gt;in &lt;/span&gt;99 ms
Jan 21 01:22:00 ansonvandoren webhook[851]: &lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/21 01:22:00 &lt;span class="o"&gt;[&lt;/span&gt;0fa3ba] finished redeploy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A quick check of the webpage shows the changes propagated correctly. Success!&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>webhooks</category>
      <category>github</category>
      <category>ufw</category>
    </item>
    <item>
      <title>Deploying a Hugo site on a GitHub commit</title>
      <dc:creator>Anson VanDoren</dc:creator>
      <pubDate>Sun, 22 Dec 2019 16:44:14 +0000</pubDate>
      <link>https://dev.to/ansonvandoren/deploying-a-hugo-site-on-a-github-commit-53ld</link>
      <guid>https://dev.to/ansonvandoren/deploying-a-hugo-site-on-a-github-commit-53ld</guid>
      <description>&lt;h2&gt;
  
  
  Doing things the hard way
&lt;/h2&gt;

&lt;p&gt;I recently helped my brother get his first website up and running. After a bit of research, we decided on static HTML, served via &lt;a href="https://caddyserver.com/"&gt;Caddy&lt;/a&gt; on a &lt;a href="https://digitalocean.com"&gt;Digital Ocean&lt;/a&gt; droplet. The choice of Caddy was predominantly due to its built-in hooks that would allow him to maintain his website as a GitHub repo, and have it automatically update every time he pushed a commit.&lt;/p&gt;

&lt;p&gt;When redesigning my website yesterday, I should have done the same thing, since our use-cases are pretty similar. But I didn't... Instead, I ended up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Digital Ocean droplet&lt;/li&gt;
&lt;li&gt;Nginx serving content&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://letsencrypt.org/"&gt;Let's Encrypt&lt;/a&gt; to avoid the 'Not Secure' message in the URL bar&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gohugo.io"&gt;Hugo&lt;/a&gt; static site generator (so I can use Markdown!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So now I'm left needing to figure out a way to implement the following workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make a change, or add a new blog post locally. Commit the change locally and push to GitHub.&lt;/li&gt;
&lt;li&gt;Magically have my website update to reflect the new content.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ideally, I want it to be as simple as that. My first implementation (&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-deploy-a-hugo-site-to-production-with-git-hooks-on-ubuntu-14-04"&gt;based on this guide&lt;/a&gt;) was more complicated, and involved setting a remote repo on my server that I could push to (in addition to GitHub). I don't like this solution because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It involves an extra step, and means I need to maintain two separate remote repos.&lt;/li&gt;
&lt;li&gt;It makes it more likely that I'll forget to push to GitHub as well, so when my server inevitably dies due to neglect or freak accident, I may not have a solid backup.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is still a workable solution, but I want it to be as streamlined as possible. In hindsight, I should have just used Caddy (&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-host-a-website-with-caddy-on-ubuntu-16-04"&gt;with this awesome tutorial&lt;/a&gt;), but sometimes I like doing things the hard way.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I need
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A GitHub webhook set up to monitor a &lt;a href="https://developer.github.com/v3/activity/events/types/#pushevent"&gt;PushEvent&lt;/a&gt; and send that along to my server.&lt;/li&gt;
&lt;li&gt;Nginx to receive the POST event from GitHub and forward it to an internal listener of some sort.&lt;/li&gt;
&lt;li&gt;Internal listener to run a shell script that will:

&lt;ul&gt;
&lt;li&gt;Fetch the latest commit from GitHub to a working directory&lt;/li&gt;
&lt;li&gt;Run Hugo to build static content based on the new commit&lt;/li&gt;
&lt;li&gt;Copy Hugo output to the appropriate folder that Nginx is serving&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Easy, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up webhook
&lt;/h2&gt;

&lt;p&gt;The go-to (pun intended) server for this seems to be &lt;a href="https://github.com/adnanh/webhook"&gt;webhook&lt;/a&gt;, written in Go by &lt;a href="https://github.com/adnanh"&gt;Adnan Hajdarević&lt;/a&gt;. I don't have a Golang environment running on the remote and don't really need one for anything else, so the &lt;code&gt;go get&lt;/code&gt; option wasn't optimal, nor was building from source. The Ubuntu package via &lt;code&gt;apt-get install webhook&lt;/code&gt; was several versions behind the latest release, so I opted to download the latest released binary instead.&lt;/p&gt;

&lt;p&gt;First I had to figure out what architecture I need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt;
x86_64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, download the correct release (x86_64 version) &lt;a href="https://github.com/adnanh/webhook/releases"&gt;from here&lt;/a&gt; and unzip it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;wget https://github.com/adnanh/webhook/releases/download/2.6.9/webhook-linux-amd64.tar.gz
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xvf&lt;/span&gt; webhook&lt;span class="k"&gt;*&lt;/span&gt;.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Make the binary available in my environment, and clean up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo mv &lt;/span&gt;webhook-linux-amd64/webhook /usr/local/bin
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; webhook-linux-amd64&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Make sure everything worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;webhook &lt;span class="nt"&gt;-version&lt;/span&gt;
webhook version 2.6.9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;webhooks&lt;/code&gt; needs a hook file (which tells it how to handle incoming requests), and a script that it should run if it matches an incoming request. I made a place for those to live, and transferred ownership to my (non-root) user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /opt/scripts
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /opt/hooks
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;:&lt;span class="nv"&gt;$USER&lt;/span&gt; /opt/scripts
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;:&lt;span class="nv"&gt;$USER&lt;/span&gt; /opt/hooks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating a hook
&lt;/h2&gt;

&lt;p&gt;Next I set up a &lt;code&gt;hooks.json&lt;/code&gt; file to allow &lt;code&gt;webhook&lt;/code&gt; to trigger on an incoming GitHub POST. I like vim, but you can use nano or whatever other editor you prefer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;vim /opt/hooks/hooks.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/adnanh/webhook/blob/master/docs/Hook-Definition.md"&gt;The documentation&lt;/a&gt; has more details about available properties, but I'll just show the configuration I ended up using.&lt;/p&gt;

&lt;p&gt;Before doing that, though, I need a secret I can share with GitHub to ensure the hook is only triggered appropriately (i.e., not by someone randomly sending a POST request to the correct endpoint). You can use anything you want as the secret, but one easy way to generate a secret is to run &lt;code&gt;uuidgen&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="nv"&gt;$ &lt;/span&gt;uuidgen
 81febb4d-4483-4fc8-a2dc-8ece300bc5f4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will output a nice long random value that is expected to be unique (&lt;a href="http://man7.org/linux/man-pages/man1/uuidgen.1.html"&gt;see here for more detail&lt;/a&gt;). Copy this value, and use it both in the JSON file below, and also when you do the next steps on GitHub. Note that the example below shows "my-github-secret" instead; you'll want to replace this in your version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"redeploy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"execute-command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/opt/scripts/redeploy.sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command-working-directory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/opt/scripts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pass-arguments-to-command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"head_commit.message"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pusher.name"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"head_commit.id"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"trigger-rule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"and"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payload-hash-sha1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"secret"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-github-secret"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"parameter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"X-Hub-Signature"&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"refs/heads/master"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"parameter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ref"&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://developer.github.com/v3/activity/events/types/"&gt;GitHub documentation&lt;/a&gt; has more details about the different payload parameters for each event type. Basically, the above hook will listen on an endpoint called &lt;code&gt;redeploy&lt;/code&gt;, and if the &lt;code&gt;trigger-rule&lt;/code&gt; is satisfied, will run the &lt;code&gt;redeploy.sh&lt;/code&gt; shell script with arguments from the POST message. This trigger rule will guarantee that it was sent from GitHub (only they have my secret), and the push happened on &lt;em&gt;master&lt;/em&gt; branch in my GitHub repo (I don't want to rebuild if the commit was only to a feature/testing branch).&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring the firewall
&lt;/h2&gt;

&lt;p&gt;Before moving on, I need to make sure my server firewall will allow the hooks through on port 9000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status
Status: active

To                         Action      From
&lt;span class="nt"&gt;--&lt;/span&gt;                         &lt;span class="nt"&gt;------&lt;/span&gt;      &lt;span class="nt"&gt;----&lt;/span&gt;
OpenSSH                    ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere
OpenSSH &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;               ALLOW       Anywhere &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;
Nginx Full &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;            ALLOW       Anywhere &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Right now, I'm only allowing ssh and normal web traffic, so I need to open up port 9000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow 9000
Rule added
Rule added &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status
Status: active

To                         Action      From
&lt;span class="nt"&gt;--&lt;/span&gt;                         &lt;span class="nt"&gt;------&lt;/span&gt;      &lt;span class="nt"&gt;----&lt;/span&gt;
OpenSSH                    ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere
9000                       ALLOW       Anywhere
OpenSSH &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;               ALLOW       Anywhere &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;
Nginx Full &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;            ALLOW       Anywhere &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;
9000 &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;                  ALLOW       Anywhere &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;DigitalOcean has a &lt;a href="https://www.digitalocean.com/community/tutorials/ufw-essentials-common-firewall-rules-and-commands"&gt;good primer on ufw&lt;/a&gt; if you need more details on configuring a firewall.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Edit #1: In the end, I decided to configure Nginx to proxy requests for &lt;a href="https://ansonvandoren.com/hooks/"&gt;https://ansonvandoren.com/hooks/&lt;/a&gt; through to my webhooks server instead, and closed port 9000 again. I may write a separate post on that since it wasn't as trivial as I wanted it to be, but the above method works just as well. The basics of what I did were from hints on &lt;a href="https://labs.lacnic.net/a-new-platform-for-lacniclabs/"&gt;this article&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Edit #2: I did actually write the subsequent post, and you can read it &lt;a href="https://ansonvandoren.com/posts/configuring-nginx-to-proxy-webhooks/"&gt;here on my blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up GitHub
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;On the GitHub page for my static site, I navigated to &lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Webhooks&lt;/strong&gt; &amp;gt; &lt;strong&gt;Add Webhook&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;For the &lt;strong&gt;Payload URL&lt;/strong&gt;, I entered: &lt;code&gt;http://ansonvandoren.com:9000/hooks/redeploy&lt;/code&gt; 
(where &lt;code&gt;redeploy&lt;/code&gt; is the id I set up in my hooks.json file).&lt;/li&gt;
&lt;li&gt;I chose &lt;strong&gt;application/json&lt;/strong&gt; for &lt;strong&gt;Content type&lt;/strong&gt;, and pasted the secret I generated with uuidgen into the &lt;strong&gt;Secret&lt;/strong&gt; field.&lt;/li&gt;
&lt;li&gt;I left &lt;strong&gt;Enable SSL verification&lt;/strong&gt; selected, and for &lt;strong&gt;Which events would you like to trigger this webhook?&lt;/strong&gt;, I chose &lt;strong&gt;Just push event&lt;/strong&gt;, and then clicked &lt;strong&gt;Add webhook&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Right away I see a notification icon showing that it failed to deliver, since I haven't actually started the webhook server yet on my remote machine. Once I do, though, GitHub will start sending POST requests every time a commit is pushed to my website repo.&lt;/p&gt;

&lt;p&gt;It is possible to set up &lt;code&gt;webhook&lt;/code&gt; to use HTTPS if you need to, but it's not as straightforward and I don't have a real need to in my case. If you need to do this, check out &lt;a href="https://github.com/adnanh/webhook#using-https"&gt;this section of the docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Edit: Using Nginx to proxy requests through to &lt;code&gt;webhooks&lt;/code&gt; as I did later actually makes it easier to use HTTPS for this, since I already had it set up for my web page thanks to Let's Encrypt.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the redeploy script
&lt;/h2&gt;

&lt;p&gt;When I created the &lt;code&gt;hooks.json&lt;/code&gt; file, I pointed it to a script called &lt;code&gt;/opt/scripts/redeploy.sh&lt;/code&gt;, so now I need to actually write that script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;vim /opt/scripts/redeploy.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Your needs may vary and you may need to customize this script in different ways than I did, but below should give you some ideas about how to proceed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash -e&lt;/span&gt;
&lt;span class="c"&gt;# Note the '-e' in the line above. This is required for error trapping implemented below.&lt;/span&gt;

&lt;span class="c"&gt;# Repo name on GitHub&lt;/span&gt;
&lt;span class="nv"&gt;REMOTE_REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://github.com/anson-vandoren/ansonvandoren.com.git
&lt;span class="c"&gt;# A place to clone the remote repo so Hugo can build from it&lt;/span&gt;
&lt;span class="nv"&gt;WORKING_DIRECTORY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/staging_area
&lt;span class="c"&gt;# Location (server block) where Nginx looks for content to serve&lt;/span&gt;
&lt;span class="nv"&gt;PUBLIC_WWW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/www/ansonvandoren.com/html
&lt;span class="c"&gt;# Backup folder in case something goes wrong during this script&lt;/span&gt;
&lt;span class="nv"&gt;BACKUP_WWW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/backup_html
&lt;span class="c"&gt;# Domain name so Hugo can generate links correctly&lt;/span&gt;
&lt;span class="nv"&gt;MY_DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ansonvandoren.com

&lt;span class="c"&gt;# For future notifications on Telegram&lt;/span&gt;
&lt;span class="nv"&gt;commit_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;pusher_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
&lt;span class="nv"&gt;commit_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;

&lt;span class="c"&gt;# If something goes wrong, put the previous version back in place&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;cleanup &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"A problem occurred. Reverting to backup."&lt;/span&gt;
    rsync &lt;span class="nt"&gt;-aqz&lt;/span&gt; &lt;span class="nt"&gt;--del&lt;/span&gt; &lt;span class="nv"&gt;$BACKUP_WWW&lt;/span&gt;/ &lt;span class="nv"&gt;$PUBLIC_WWW&lt;/span&gt;
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$WORKING_DIRECTORY&lt;/span&gt;
    &lt;span class="c"&gt;# !!Placeholder for Telegram notification&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Call the cleanup function if this script exits abnormally. The -e flag&lt;/span&gt;
&lt;span class="c"&gt;# in the shebang line ensures an immediate abnormal exit on any error&lt;/span&gt;
&lt;span class="nb"&gt;trap &lt;/span&gt;cleanup EXIT

&lt;span class="c"&gt;# Clear out the working directory&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$WORKING_DIRECTORY&lt;/span&gt;
&lt;span class="c"&gt;# Make a backup copy of current website version&lt;/span&gt;
rsync &lt;span class="nt"&gt;-aqz&lt;/span&gt; &lt;span class="nv"&gt;$PUBLIC_WWW&lt;/span&gt;/ &lt;span class="nv"&gt;$BACKUP_WWW&lt;/span&gt;

&lt;span class="c"&gt;# Clone the new version from GitHub&lt;/span&gt;
git clone &lt;span class="nv"&gt;$REMOTE_REPO&lt;/span&gt; &lt;span class="nv"&gt;$WORKING_DIRECTORY&lt;/span&gt;

&lt;span class="c"&gt;# !!Placeholder for Telegram notification&lt;/span&gt;

&lt;span class="c"&gt;# Delete old version&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$PUBLIC_WWW&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="c"&gt;# Have Hugo generate the new static HTML directly into the public WWW folder&lt;/span&gt;
/usr/local/bin/hugo &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$WORKING_DIRECTORY&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;$PUBLIC_WWW&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MY_DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# !!Placeholder for Telegram notification&lt;/span&gt;

&lt;span class="c"&gt;# Clear out working directory&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$WORKING_DIRECTORY&lt;/span&gt;
&lt;span class="c"&gt;# Exit without trapping, since everything went well&lt;/span&gt;
&lt;span class="nb"&gt;trap&lt;/span&gt; - EXIT
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With the comments, I think most of that should be clear enough. There's some additional material on bash error trapping (it was brand new to me) that &lt;a href="http://redsymbol.net/articles/bash-exit-traps/"&gt;can be found here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The script needs to be executable so the hook can run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /opt/scripts/redeploy.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I wanted to make sure the script ran OK before moving on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bash /opt/scripts/redeploy.sh
Cloning into &lt;span class="s1"&gt;'/home/blog/staging_area'&lt;/span&gt;...
remote: Enumerating objects: 155, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
remote: Counting objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;155/155&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
remote: Compressing objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;125/125&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
remote: Total 155 &lt;span class="o"&gt;(&lt;/span&gt;delta 21&lt;span class="o"&gt;)&lt;/span&gt;, reused 145 &lt;span class="o"&gt;(&lt;/span&gt;delta 11&lt;span class="o"&gt;)&lt;/span&gt;, pack-reused 0
Receiving objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;155/155&lt;span class="o"&gt;)&lt;/span&gt;, 620.23 KiB | 7.13 MiB/s, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Resolving deltas: 100% &lt;span class="o"&gt;(&lt;/span&gt;21/21&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

                   | EN
+------------------+----+
  Pages            | 15
  Paginator pages  |  0
  Non-page files   |  0
  Static files     |  5
  Processed images |  0
  Aliases          |  5
  Sitemaps         |  1
  Cleaned          |  0

Total &lt;span class="k"&gt;in &lt;/span&gt;47 ms
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing with webhooks
&lt;/h2&gt;

&lt;p&gt;Everything looks good, and the website is still working. Let's try a real test by running webhooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;webhook &lt;span class="nt"&gt;-hooks&lt;/span&gt; /opt/hooks/hooks.json &lt;span class="nt"&gt;-verbose&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/20 21:46:23 version 2.6.9 starting
&lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/20 21:46:23 setting up os signal watcher
&lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/20 21:46:23 attempting to load hooks from /opt/hooks/hooks.json
&lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/20 21:46:23 found 1 hook&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;file
&lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/20 21:46:23   loaded: redeploy
&lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/20 21:46:23 serving hooks on http://0.0.0.0:9000/hooks/&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/20 21:46:23 os signal watcher ready
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, from my local machine, I'll create a test commit and push to GitHub&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;--allow-empty&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Trigger test"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;master eb3fdc6] Trigger &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git push origin master
Enumerating objects: 1, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Counting objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;1/1&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Writing objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;1/1&lt;span class="o"&gt;)&lt;/span&gt;, 197 bytes | 197.00 KiB/s, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Total 1 &lt;span class="o"&gt;(&lt;/span&gt;delta 0&lt;span class="o"&gt;)&lt;/span&gt;, reused 0 &lt;span class="o"&gt;(&lt;/span&gt;delta 0&lt;span class="o"&gt;)&lt;/span&gt;
To github.com:anson-vandoren/ansonvandoren.com.git
   c12a30d..eb3fdc6  master -&amp;gt; master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It worked! Here's the output from webhooks back on the remote machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/20 21:46:34 &lt;span class="o"&gt;[&lt;/span&gt;dfa235] incoming HTTP request from 192.30.252.37:39834
&lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/20 21:46:34 &lt;span class="o"&gt;[&lt;/span&gt;dfa235] redeploy got matched
&lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/20 21:46:34 &lt;span class="o"&gt;[&lt;/span&gt;dfa235] redeploy hook triggered successfully
&lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/20 21:46:34 200 | 1.222118ms | ansonvandoren.com:9000 | POST /hooks/redeploy
&lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/20 21:46:34 &lt;span class="o"&gt;[&lt;/span&gt;dfa235] executing /opt/scripts/redeploy.sh &lt;span class="o"&gt;(&lt;/span&gt;/opt/scripts/redeploy.sh&lt;span class="o"&gt;)&lt;/span&gt; with arguments &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/opt/scripts/redeploy.sh"&lt;/span&gt; &lt;span class="s2"&gt;"Trigger notification5"&lt;/span&gt; &lt;span class="s2"&gt;"anson-vandoren"&lt;/span&gt; &lt;span class="s2"&gt;"4a1...7a2"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; and environment &lt;span class="o"&gt;[]&lt;/span&gt; using /opt/scripts as cwd
&lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/20 21:46:35 &lt;span class="o"&gt;[&lt;/span&gt;dfa235] &lt;span class="nb"&gt;command &lt;/span&gt;output: Cloning into &lt;span class="s1"&gt;'/home/blog/staging_area'&lt;/span&gt;...
Building sites …
                   | EN
+------------------+----+
  Pages            | 15
  Paginator pages  |  0
  Non-page files   |  0
  Static files     |  5
  Processed images |  0
  Aliases          |  5
  Sitemaps         |  1
  Cleaned          |  0

Total &lt;span class="k"&gt;in &lt;/span&gt;40 ms

&lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/20 21:46:35 &lt;span class="o"&gt;[&lt;/span&gt;dfa235] finished handling redeploy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding webhooks as a service
&lt;/h2&gt;

&lt;p&gt;Since I'll be running this on a remote machine, I want to set it up as a systemd service so it will come back up if the server reboots for any reason. It took me a while to settle on the "right" way to do this one, but I think this should work well.&lt;/p&gt;

&lt;p&gt;First, I need to create a service file as my non-root user, in the home directory as shown below. Again, you can use nano if you're not comfortable with vim.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;vim ~/.config/systemd/user/webhook.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The contents of this file should look similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;Unit]
&lt;span class="nv"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Simple Golang webhook server
&lt;span class="nv"&gt;ConditionPathExists&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/bin/webhook
&lt;span class="nv"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network.target

&lt;span class="o"&gt;[&lt;/span&gt;Service]
&lt;span class="nv"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;simple
&lt;span class="nv"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/blog
&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/bin/webhook &lt;span class="nt"&gt;-ip&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;-hooks&lt;/span&gt; /opt/hooks/hooks.json &lt;span class="nt"&gt;-verbose&lt;/span&gt;
&lt;span class="nv"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on-failure
&lt;span class="nv"&gt;PrivateTmp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;Install]
&lt;span class="nv"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default.target
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For a complete descriptions of the sections of a service file, you can check out &lt;a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/sect-managing_services_with_systemd-unit_files"&gt;this documentation&lt;/a&gt; from RedHat.&lt;/p&gt;

&lt;p&gt;Next I needed to ensure that my non-root user (shown below as 'myuser', but substitute whatever login you are using for this) could keep a process running even when not logged in. After enabling 'lingering', I enabled my new service and started it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;loginctl enable-linger myuser
&lt;span class="nv"&gt;$ &lt;/span&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nb"&gt;enable &lt;/span&gt;webhook
&lt;span class="nv"&gt;$ &lt;/span&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; start webhook
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Everything should be working correctly now, so I ran another empty commit/push from my local machine, and then checked the status of the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; status webhook
● webhook.service - Simple Golang webhook server
   Loaded: loaded &lt;span class="o"&gt;(&lt;/span&gt;/home/blog/.config/systemd/user/webhook.service&lt;span class="p"&gt;;&lt;/span&gt; enabled&lt;span class="p"&gt;;&lt;/span&gt; vendor preset: enabled&lt;span class="o"&gt;)&lt;/span&gt;
   Active: active &lt;span class="o"&gt;(&lt;/span&gt;running&lt;span class="o"&gt;)&lt;/span&gt; since Mon 2019-01-21 00:34:03 UTC&lt;span class="p"&gt;;&lt;/span&gt; 50s ago
 Main PID: 20422 &lt;span class="o"&gt;(&lt;/span&gt;webhook&lt;span class="o"&gt;)&lt;/span&gt;
   CGroup: /user.slice/user-1000.slice/user@1000.service/webhook.service
           └─20422 /usr/local/bin/webhook &lt;span class="nt"&gt;-ip&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;-hooks&lt;/span&gt; /opt/hooks/hooks.json &lt;span class="nt"&gt;-verbose&lt;/span&gt;

Jan 21 00:34:49 ansonvandoren webhook[20422]:   Pages            | 15
Jan 21 00:34:49 ansonvandoren webhook[20422]:   Paginator pages  |  0
Jan 21 00:34:49 ansonvandoren webhook[20422]:   Non-page files   |  0
Jan 21 00:34:49 ansonvandoren webhook[20422]:   Static files     |  5
Jan 21 00:34:49 ansonvandoren webhook[20422]:   Processed images |  0
Jan 21 00:34:49 ansonvandoren webhook[20422]:   Aliases          |  5
Jan 21 00:34:49 ansonvandoren webhook[20422]:   Sitemaps         |  1
Jan 21 00:34:49 ansonvandoren webhook[20422]:   Cleaned          |  0
Jan 21 00:34:49 ansonvandoren webhook[20422]: Total &lt;span class="k"&gt;in &lt;/span&gt;53 ms
Jan 21 00:34:49 ansonvandoren webhook[20422]: &lt;span class="o"&gt;[&lt;/span&gt;webhook] 2019/01/21 00:34:49 &lt;span class="o"&gt;[&lt;/span&gt;67d5a4] finished handling
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It worked! Just to make sure (I'm a little paranoid), I rebooted my remote machine, and checked again when it came back up. Everything worked as advertised, and webhook is running again, waiting for my next git commit.&lt;/p&gt;

&lt;p&gt;The only thing I still need to set up is the Telegram notifications for build/deploy status. This post is long enough as it is, and that's almost an entirely separate topic, so I'll leave that for a separate post that you &lt;a href="https://ansonvandoren.com/posts/telegram-notification-on-deploy/"&gt;can find here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was first originally published &lt;a href="https://ansonvandoren.com"&gt;on my own blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>hugo</category>
      <category>bash</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
