<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-03-27T01:01:27-04:00</updated><id>/feed.xml</id><title type="html">Shen’s Blog</title><entry><title type="html">The Hidden Contract in Every API Call</title><link href="/2026/03/26/hidden-contract-in-every-api-call.html" rel="alternate" type="text/html" title="The Hidden Contract in Every API Call" /><published>2026-03-26T00:00:00-04:00</published><updated>2026-03-26T00:00:00-04:00</updated><id>/2026/03/26/hidden-contract-in-every-api-call</id><content type="html" xml:base="/2026/03/26/hidden-contract-in-every-api-call.html"><![CDATA[<h2 id="the-tv">The TV</h2>

<p>I got a TCL TV with Fire TV on Amazon Prime Day last year(2025). It’s a decent deal for the price, but the CPU is noticeably underpowered for the operating system. Most of time it’s functional enough that I appreciate it’s existence.
Sometimes though, I want to throw a brick at it. The most infuriating case is when I browse Netflix, land on an interesting show, and press “OK” to see more details. Nothing happens. So I press it again. Then, after some silence and janky animations, the show starts playing, and then I’d struggle to go back, and then Netflix would mark that show as something I started watching. A show I never intended to watch becomes a forever mark in my identity, according to Netflix.</p>

<h2 id="a-distributed-system">A Distributed System</h2>

<p>And when I think about this, the root cause is actually a classic <strong>replication failure</strong>. There are two nodes in the system, me and the machine. And our state were out of sync. You may disagree with this, and argue that it’s ridiculous to model human beings as nodes in a system. However, the way I think about it, is that <strong>whenever the rate of action in a system is faster than how fast a node, who acts based on local state, can keep up, the problems and challenges of a distributed systems emerges</strong>. This applies to computer to computer systems, computer to human systems, and even human to human systems(teams, organizations, etc.) too.</p>

<h2 id="web-apps-are-always-distributed-systems">Web Apps Are ALWAYS Distributed Systems</h2>

<p>By this definition, web apps are always distributed systems, because, well, there’s always lag between the client and server, other users might mutate state, background processes on the server can mutate state, time is always passing, etc.</p>

<blockquote>
  <p>Here I’m assuming there’s no lag between human and the client, user’s actions are timely, relative to user’s rate of action, reflected on their screen.</p>
</blockquote>

<p>In a distributed system, you can never safely assume that one node(the client)’s world state matches the other(server)’s. And yet, the vast majority of APIs are designed as if this isn’t true. They accept an action, which is generated by the client based on client’s state, execute it against current server state, and return a result or an error, leaving the client’s assumptions unvalidated. You would not do this in a distributed backend system.</p>

<p>This is the gap I want to talk about: <strong>the client’s observed world state is an implicit input to every API call, but most APIs never ask enough of it.</strong></p>

<h3 id="what-this-looks-like-in-practice">What This Looks Like in Practice</h3>

<p>Consider a checkout API:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /checkout
{
  "cart_id": "12345"
}
</code></pre></div></div>

<p>This tells the server <em>what to do</em>, but nothing about <em>what the client believes to be true</em> when asking. If the user had the cart open in two tabs and added an item in the other one, this request will silently checkout with items the user may not have intended to buy. If the price of an item changed after the user added it to their cart, they might be charged more than they expected.</p>

<p>A state-aware version changes the contract:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /checkout
{
  "cart_id": "12345",
  # assume the cart version updates whenever the cart item changes or price updates
  "cart_version": "xxxx"
}
</code></pre></div></div>

<p>Now the server can validate: “is this the cart state the client was looking at?” If not, it rejects the request with a meaningful error instead of executing against a world the client didn’t see.</p>

<p>There are subtler examples too. Consider this API that’s called as the last step of enabling a paid feature during onboarding:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /enable_feature
{
  "feature_name": "new_cool_feature"
}
</code></pre></div></div>

<p>On the backend, whether this generates an invoice depends on the user’s current plan. If the client doesn’t declare what it <em>thinks</em> will happen, you can end up charging a user who believed it was free, or skipping a charge for someone who agreed to pay. In cases where the user is acknowledging a pricing contract, it leads to financial or legal damage, because the client and the server may have different understandings of what the user agreed to to.</p>

<p>A better design would look like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /enable_feature
{
  "feature_name": "new_cool_feature",
  "acknowledged_tos": "paid_plan_tos_v3",
  "acknowledged_pricing": "paid_plan_pricing_v3"
}
</code></pre></div></div>

<p>The server now validates that the client’s understanding of the situation matches reality before doing anything irreversible.</p>

<h3 id="some-existing-practices-are-already-doing-this">Some Existing Practices Are Already Doing This</h3>

<p>None of this is entirely new, the industry has developed several patterns that are, at their core, ways of encoding client world state into requests. They just haven’t been unified under this framing.</p>

<p><strong>Idempotency keys</strong> are a client’s way of saying “this is what I am naming my world in which this operation is to be performed.” The server can use this to detect and reject duplicate requests from a stale view. (Although in practice, this can be implemented in a way before a request reaching the business logic or even before the app layer, but it still fits in the frame of one node claiming a world state to another node.)</p>

<p><strong>Base version checks</strong> in version control work the same way. When you push a commit, you’re declaring what branch state you were working from. If someone else pushed in the meantime, your assumed base no longer matches reality, and the push is rejected - saving you from silently overwriting their work. This also allows the server to return useful error messages like “push rejected because your branch is out of date, you can force push or pull and merge first”.</p>

<p><strong>Authentication headers</strong> are also a form of world state declaration: “I am this identity, and I expect you to treat this request through that lens.” The server validates the claim before proceeding.</p>

<p>Each of these is solving the same underlying problem in a narrow domain. What I’m arguing is that <strong>expressing client world state should be a first-class concept in API design</strong>, applied broadly and deliberately rather than as a case-by-case patch.</p>

<h2 id="designing-for-state-divergence-in-apis">Designing for State Divergence in APIs</h2>

<p>Treating world state as an explicit input has a few practical consequences for API design.</p>

<p>When the declared state doesn’t match server state, the API can return a specific, actionable error instead of silently succeeding against a world the client didn’t see, or throwing a non-actionable, generic error message. This is the difference between “checkout failed because your cart was updated” and a mysterious wrong order or a geneic “failed, please try again”.</p>

<p>It also forces a useful discipline on the API designer. Before finalizing any endpoint, ask:</p>
<ul>
  <li>What’s the user intention for an API call?</li>
  <li>What does the client need to believe for this intention to make sense?</li>
  <li>What’s a good way to encode the intention and the client’s belief in the API contract?</li>
  <li>If client and server’s world view have diverged, what’s the worst thing that silently happens?</li>
  <li>What error can I return that helps the client or user understand what happened and how to fix forward?</li>
</ul>

<p>At the end of the day, every web app is a distributed system. We spend lots of time designing for distributed state on the backend, but we often forget that the client, and the human is part of that system too.</p>

<blockquote>
  <p>PS: if you’ve ever worked on any web app, you should confidently state on your resume that you have experience working with distributed systems. Because you do.</p>
</blockquote>]]></content><author><name></name></author><category term="API" /><summary type="html"><![CDATA[Web apps are distributed systems. The client's observed world state should be an explicit input to every API call.]]></summary></entry><entry><title type="html">Django Deployment Goals for Beginners</title><link href="/2020/10/24/django-deployment-goals-for-beginners.html" rel="alternate" type="text/html" title="Django Deployment Goals for Beginners" /><published>2020-10-24T11:39:00-04:00</published><updated>2020-10-24T11:39:00-04:00</updated><id>/2020/10/24/django-deployment-goals-for-beginners</id><content type="html" xml:base="/2020/10/24/django-deployment-goals-for-beginners.html"><![CDATA[<p>This short post is not a step by step guide on how to deploy Django on a Linux machine. It tries to explain to readers what are the goals of a reasonable Django deployment and how they can be achieved so when reading other tutorials online readers can have a better picture of what’s actually going on.</p>

<h2 id="who-speaks-what">Who speaks what</h2>
<p>One way I like to think about Django deployment, or any kind of Web based service deployment, is by looking at who speaks what. In the context of Django, we know that Django itself speaks WSGI (or ASGI, for simplicity, I’ll use WSGI as the example) and the user’s browser speaks HTTP. So, apparently, there’s some translation needed. We need something that translates WSGI to HTTP. In many Django applications, the users would also like to access static files, such as images, style sheets, JavaScript files and files that are uploaded by users, these files usually lives in a file system, and they speak file system language, not WSGI, not HTTP, so there are translations to be done here too.</p>

<p>With this in mind, we can treat the server software as translators that can translates a language to another.</p>

<h2 id="goals">Goals</h2>
<p>Because our Web services are eventually consumed by web browsers or other HTTP clients, the end goal of a successful deployment is to translate everything into HTTP and providing a single HTTP “access point” who knows how to pass things to other translators based on the requested path. As discussed above, there are really only two things needed to be translated: WSGI and files.</p>

<h2 id="wsgi-to-http">WSGI to HTTP</h2>
<p>Things that translate WSGI to HTTP are called WSGI servers. They receive HTTP requests and translate them into <em>WSGI environment variables</em> and then calls your WSGI entry point with the <em>WSGI environment variables</em>, for Django, the WSGI entry point can be retrieved by calling <code class="language-plaintext highlighter-rouge">django.core.wsgi.get_wsgi_application</code> or is readily available in your projects’ <code class="language-plaintext highlighter-rouge">wsgi.py</code> file as <code class="language-plaintext highlighter-rouge">application</code>. There are plenty of WSGI servers to choose from, some common choices includes <code class="language-plaintext highlighter-rouge">gunicorn</code> and <code class="language-plaintext highlighter-rouge">uWsgi</code>. Django’s <code class="language-plaintext highlighter-rouge">runserver</code> command also starts a WSGI server. One thing to note though, is that <code class="language-plaintext highlighter-rouge">uwsgi</code> the protocol used by <code class="language-plaintext highlighter-rouge">uWSGI</code> is its own language, it is not HTTP and not WSGI, but <code class="language-plaintext highlighter-rouge">uwsgi</code> can be understood by <code class="language-plaintext highlighter-rouge">nginx</code>, a popular HTTP server, and you can configure <code class="language-plaintext highlighter-rouge">uWSGI</code> to speak HTTP directly. To simplify things, we can treat <code class="language-plaintext highlighter-rouge">uWSGI</code> and <code class="language-plaintext highlighter-rouge">nginx</code> together as a translator that translates WSGI to HTTP.</p>

<h2 id="file-to-wsgi-to-http">File (to WSGI) to HTTP</h2>
<p>Now that WSGI to HTTP is handled, we have two path ahead of us for handling files on a file system.</p>

<p>The first one is to translate files to WSGI and because we already know how to translate WSGI to HTTP, the path from file to HTTP is also established.</p>

<p>The other one is to translate file directly to HTTP, this one is more efficient as you only need one translator instead of two. The problem is that (almost) all the WSGI servers can’t do this natively, meaning you can’t ask them to only translate paths that doesn’t starts with <code class="language-plaintext highlighter-rouge">/static</code>. To do this, you need a more generic HTTP server or proxy, such as <code class="language-plaintext highlighter-rouge">nginx</code> or <code class="language-plaintext highlighter-rouge">caddy</code>, you can ask them to serve files directly when the path starts with <code class="language-plaintext highlighter-rouge">/static</code>, otherwise, pass the HTTP request to your WSGI server. Note that it would be impossible to do this when we don’t have access to the HTTP server configuration, for example, when using Heroku.</p>

<p>Django’s built-in development server goes the first path, it turns all HTTP requests to WSGI and inside WSGI application if it sees the requested URL matches that of a static file, it then reads the file and returns its content. There are other libraries that does this better than Django’s built-in server, such as <code class="language-plaintext highlighter-rouge">whitenoise</code>. This approach is not completely unusable, especially when you have a CDN server that caches all static resources, so future requests only hits the CDN server not your Django applications.</p>

<h2 id="non-django-specific-goals-and-tools">Non Django-specific goals and tools</h2>
<p>When doing a production deployment, it’s not enough to just Django running. We need users to visit our website though a domain name, so a DNS record that points to our server will be required and port 80 will need to be open on the server. For security, we would like to provide HTTPS accessibility too. To do this, we can use <code class="language-plaintext highlighter-rouge">nginx</code> with some plugin to automatically request SSL certificates from <code class="language-plaintext highlighter-rouge">Let's Encrypt</code>. Cloudflare is a popular CDN/DNS service provider that have built-in support for HTTPS and caching.</p>

<p>It is also desired to have some kind of process manager to run our WSGI servers instead of running it on the foreground in the shell. Process managers allows us to monitor the status of servers, auto start servers when  machine reboots, scale servers to have more processes and other benefits. <code class="language-plaintext highlighter-rouge">supervisor</code> is a popular option in Django community as a simple process manager.</p>

<h2 id="uwsgi-goodies">uWSGI Goodies</h2>
<p>As mentioned above, uWSGI can be configured to speak HTTP directly. Turns out, it can also be configured to serve static files. So you really can do a Django deployment with only uWSGI. Here’s an example configuration:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[uwsgi]</span><span class="w">
</span><span class="py">http</span><span class="w">            </span><span class="p">=</span><span class="w"> </span><span class="s">0.0.0.0:80</span>
<span class="py">static-map</span><span class="w">      </span><span class="p">=</span><span class="w"> </span><span class="s">/static=/myproj/static</span>
<span class="py">static-map</span><span class="w">      </span><span class="p">=</span><span class="w"> </span><span class="s">/media=/myproj/media</span>
<span class="py">chdir</span><span class="w">           </span><span class="p">=</span><span class="w"> </span><span class="s">/myproj</span>
<span class="py">module</span><span class="w">          </span><span class="p">=</span><span class="w"> </span><span class="s">myproj.wsgi</span>
<span class="py">home</span><span class="w">            </span><span class="p">=</span><span class="w"> </span><span class="s">/myproj/venv  # this is the virtualenv path</span>
<span class="py">master</span><span class="w">          </span><span class="p">=</span><span class="w"> </span><span class="s">true</span>
<span class="py">processes</span><span class="w">       </span><span class="p">=</span><span class="w"> </span><span class="s">10</span>
<span class="py">vacuum</span><span class="w">          </span><span class="p">=</span><span class="w"> </span><span class="s">true</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="Django" /><summary type="html"><![CDATA[What are we actually doing when deploying Django applications]]></summary></entry><entry><title type="html">Seamlessly integrate hashids with Django</title><link href="/2020/08/26/django-hashids.html" rel="alternate" type="text/html" title="Seamlessly integrate hashids with Django" /><published>2020-08-26T23:45:00-04:00</published><updated>2020-08-26T23:45:00-04:00</updated><id>/2020/08/26/django-hashids</id><content type="html" xml:base="/2020/08/26/django-hashids.html"><![CDATA[<h1 id="introduction">Introduction</h1>
<p>Hashids is a library that maps an integer to a string with provided <code class="language-plaintext highlighter-rouge">salt</code>, <code class="language-plaintext highlighter-rouge">alphabet</code>, and <code class="language-plaintext highlighter-rouge">min_length</code>. For example, it can turn integer <code class="language-plaintext highlighter-rouge">1</code> into <code class="language-plaintext highlighter-rouge">"r87f"</code> and convert it back when required.</p>

<p>It is a way to obfuscate ids and is particularly useful when you don’t want anyone to iterate through everything in your database by going through all the ids.</p>

<p>I have used Hashids a few times with Django. And every time I need to expose the <code class="language-plaintext highlighter-rouge">id</code> field, I would convert the <code class="language-plaintext highlighter-rouge">id</code> value to a hashids by calling something like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">utils</span> <span class="kn">import</span> <span class="n">hashids</span>

<span class="k">def</span> <span class="nf">to_json</span><span class="p">(</span><span class="n">obj</span><span class="p">):</span>
    <span class="n">exposed_id</span> <span class="o">=</span> <span class="n">hashids</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">obj</span><span class="p">.</span><span class="nb">id</span><span class="p">)</span>
    <span class="bp">...</span>
    <span class="k">return</span> <span class="p">{</span>
        <span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">:</span> <span class="n">exposed_id</span><span class="p">,</span>
        <span class="bp">...</span>
    <span class="p">}</span>
</code></pre></div></div>
<p>And everywhere that <code class="language-plaintext highlighter-rouge">exposed_id</code> is used I need to convert it back, which ended up as a lot of code in different places.</p>

<p>Another issue with this approach is that it’s hard to use different configurations, such as salt and alphabet, for different models. The <code class="language-plaintext highlighter-rouge">exposed_id</code> for different models with the same actual <code class="language-plaintext highlighter-rouge">id</code> will be the same.</p>

<p>There are some existing projects that integrate the two, but they are more intrusive than I would like them to be. As they usually actually writes to the database, instead of just encode/decode between obfuscated id and integer ids on the fly.</p>

<p>This leads to this small library I made with less than 100 lines of code, called <a href="https://github.com/ericls/django-hashids"><code class="language-plaintext highlighter-rouge">django-hashids</code></a>.</p>

<h1 id="django-hashids">django-hashids</h1>
<p><a href="https://github.com/ericls/django-hashids"><code class="language-plaintext highlighter-rouge">django-hashids</code></a> integrates Django with Hashids by introducing a “virtual” field to Django models. It is “virtual” because it does not have a column in the database but allows people to query it as if there were an actual database column.</p>

<p>Here’s a simple example:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TestModel</span><span class="p">(</span><span class="n">Model</span><span class="p">):</span>
    <span class="n">hashid</span> <span class="o">=</span> <span class="nc">HashidsField</span><span class="p">(</span><span class="n">real_field_name</span><span class="o">=</span><span class="sh">"</span><span class="s">id</span><span class="sh">"</span><span class="p">)</span>

<span class="n">instance</span> <span class="o">=</span> <span class="n">TestModel</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nf">create</span><span class="p">()</span>
<span class="n">instance2</span> <span class="o">=</span> <span class="n">TestModel</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nf">create</span><span class="p">()</span>
<span class="n">instance</span><span class="p">.</span><span class="nb">id</span>  <span class="c1"># 1
</span><span class="n">instance2</span><span class="p">.</span><span class="nb">id</span>  <span class="c1"># 2
</span>
<span class="c1"># Allows access to the field
</span><span class="n">instance</span><span class="p">.</span><span class="n">hashid</span>  <span class="c1"># '1Z'
</span><span class="n">instance2</span><span class="p">.</span><span class="n">hashid</span>  <span class="c1"># '4x'
</span>
<span class="c1"># Allows querying by the field
</span><span class="n">TestModel</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">hashid</span><span class="o">=</span><span class="sh">"</span><span class="s">1Z</span><span class="sh">"</span><span class="p">)</span>
<span class="n">TestModel</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nf">filter</span><span class="p">(</span><span class="n">hashid</span><span class="o">=</span><span class="sh">"</span><span class="s">1Z</span><span class="sh">"</span><span class="p">)</span>
<span class="n">TestModel</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nf">filter</span><span class="p">(</span><span class="n">hashid__in</span><span class="o">=</span><span class="p">[</span><span class="sh">"</span><span class="s">1Z</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">4x</span><span class="sh">"</span><span class="p">])</span>
<span class="n">TestModel</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nf">filter</span><span class="p">(</span><span class="n">hashid__gt</span><span class="o">=</span><span class="sh">"</span><span class="s">1Z</span><span class="sh">"</span><span class="p">)</span>  <span class="c1"># same as id__gt=1, would return instance 2
</span>
<span class="c1"># Allows usage in queryset.values
</span><span class="n">TestModel</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nf">values_list</span><span class="p">(</span><span class="sh">"</span><span class="s">hashid</span><span class="sh">"</span><span class="p">,</span> <span class="n">flat</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="c1"># ["1Z", "4x"]
</span><span class="n">TestModel</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nf">filter</span><span class="p">(</span><span class="n">hashid__in</span><span class="o">=</span><span class="n">TestModel</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nf">values</span><span class="p">(</span><span class="sh">"</span><span class="s">hashid</span><span class="sh">"</span><span class="p">))</span>
</code></pre></div></div>
<p>As you can see, it allows you to use <code class="language-plaintext highlighter-rouge">TestModel.hashid</code> like a real field with all the sensible lookups but queries are proxies to <code class="language-plaintext highlighter-rouge">id</code> field with encoding/decoding happening on the fly providing a seamless experience.</p>

<blockquote>
  <p>For more usage and configuration options please visit <a href="https://github.com/ericls/django-hashids">django-hashids on github</a></p>
</blockquote>]]></content><author><name></name></author><category term="Django" /><summary type="html"><![CDATA[What is hashids, why it's useful, the challenges I had when using it in django and how I solved them.]]></summary></entry><entry><title type="html">ASGI from scratch - WebSocket</title><link href="/2020/08/05/asgi-from-scratch-2-websocket.html" rel="alternate" type="text/html" title="ASGI from scratch - WebSocket" /><published>2020-08-05T21:21:00-04:00</published><updated>2020-08-05T21:21:00-04:00</updated><id>/2020/08/05/asgi-from-scratch-2-websocket</id><content type="html" xml:base="/2020/08/05/asgi-from-scratch-2-websocket.html"><![CDATA[<h1 id="intro">Intro</h1>
<p>This is a follow up to my last blog post: <a href="https://shenli.dev/2020/06/20/asgi-from-scratch.html">ASGI from scratch - Let’s build an ASGI web framework</a> where I walked through the steps to build a simple but somewhat functional ASGI application framework. In that post, only HTTP is implemented and in this post, I’m going to go through the steps to add WebSocket support to the framework.</p>

<h1 id="goal">Goal</h1>
<p>There are many ways to add WebSocket support to an ASGI framework. And I think ASGI’s WebSocket specification is already a good enough abstraction around WebSocket protocol. How it’s going to be implemented is very dependent on what kind of interface I want to provide to the framework’s users.</p>

<p>Assume I want to provide an interface as shown below, where the user can accept the connection and then read data from the client in an async generator.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># example-ws.py
</span><span class="kn">from</span> <span class="n">aaf</span> <span class="kn">import</span> <span class="n">aaf</span>
<span class="kn">from</span> <span class="n">aaf.routing</span> <span class="kn">import</span> <span class="n">Router</span>

<span class="n">router</span> <span class="o">=</span> <span class="nc">Router</span><span class="p">()</span>

<span class="nd">@router.route</span><span class="p">(</span><span class="sh">'</span><span class="s">/ws_echo</span><span class="sh">'</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">ws_echo</span><span class="p">(</span><span class="n">connection</span><span class="p">):</span>
    <span class="k">await</span> <span class="n">connection</span><span class="p">.</span><span class="nf">accept</span><span class="p">()</span>
    <span class="k">async</span> <span class="k">for</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">connection</span><span class="p">.</span><span class="nf">iter_messages</span><span class="p">():</span>
        <span class="nf">if </span><span class="p">(</span><span class="n">message</span> <span class="o">==</span> <span class="sh">"</span><span class="s">QUIT</span><span class="sh">"</span><span class="p">):</span>
            <span class="k">await</span> <span class="n">connection</span><span class="p">.</span><span class="nf">finish</span><span class="p">(</span><span class="n">close_code</span><span class="o">=</span><span class="mi">1000</span><span class="p">)</span>
            <span class="k">break</span>
        <span class="k">await</span> <span class="n">connection</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">disconnected</span><span class="sh">"</span><span class="p">)</span>


<span class="n">app</span> <span class="o">=</span> <span class="nf">aaf</span><span class="p">([</span><span class="n">router</span><span class="p">])</span>
</code></pre></div></div>
<blockquote>
  <p>Implementation of<code class="language-plaintext highlighter-rouge">Rouotuer</code> class and <code class="language-plaintext highlighter-rouge">aff</code> function was covered in <a href="https://shenli.dev/2020/06/20/asgi-from-scratch.html">previous post</a></p>
</blockquote>

<h1 id="websocket-in-asgi">WebSocket in ASGI</h1>
<p>Here’s a simple diagram of the lifespan of a WebSocket connection in the context of ASGI.</p>
<pre><code class="language-mermaid"> sequenceDiagram

  

  Note over Client,Server: WebSocket

  Note over Server,Application: ASGI

  Client-&gt;&gt;Server: Handshake

  Server-&gt;&gt;Application: "websocket.connect"

  alt Should accept connection?

    Application-&gt;&gt;Server: "websocket.accept"

    Server-&gt;&gt;Client: Http 101

  else 

    Application-&gt;&gt;Server: "websocket.close"

    Server-&gt;&gt;Client: Http 403

  end

  

  rect aliceblue

    loop heartbeat

      Client--&gt;Server: ping

      Client--&gt;Server: pong

    end

    Client--&gt;&gt;Server: data frames

    Server--&gt;&gt;Application: "websocket.receive"

    Application--&gt;&gt;Server: "websocket.send"

    Server--&gt;&gt;Client: data frames

  end

  

  alt Client/Server discnnect

    Client-&gt;Server: Closed/lost connection


  else application close connection

    Application-&gt;&gt;Server: "websocket.close"

    Server-&gt;Client: Discnnect

  end
 
  Server-&gt;&gt;Application: "websocket.disconnect"
  
</code></pre>

<p>It shows when each type of ASGI messages are sent and received. Every WebSocket connection starts with a <code class="language-plaintext highlighter-rouge">websocket.connect</code> message. When this message is received, the application can send a <code class="language-plaintext highlighter-rouge">websocket.accept</code> to accept a connection or a <code class="language-plaintext highlighter-rouge">websocket.close</code> message to reject it. After this handshake, data frames can be exchanged using <code class="language-plaintext highlighter-rouge">websocket.send</code> and <code class="language-plaintext highlighter-rouge">websocket.receive</code> messages. Both the application and client can initiate a disconnection, the application can do this by sending a <code class="language-plaintext highlighter-rouge">websocket.close</code> message. The server will send a <code class="language-plaintext highlighter-rouge">websocket.disconnect</code> message to the application whenever the connection between client and server is closed or lost.</p>

<h1 id="implementation">Implementation</h1>

<h2 id="accept-or-reject-connection">Accept or Reject Connection</h2>

<p>To accept a connection, we can simply send a ASGI message with type <code class="language-plaintext highlighter-rouge">websocket.accept</code> along with <code class="language-plaintext highlighter-rouge">subprotocol</code> and <code class="language-plaintext highlighter-rouge">headers</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Connection</span><span class="p">:</span>
    <span class="bp">...</span>
    <span class="k">async</span> <span class="k">def</span> <span class="nf">accept</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">subprotocol</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="n">headers</span> <span class="o">=</span> <span class="p">[</span>
            <span class="p">[</span><span class="n">k</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="sh">"</span><span class="s">ascii</span><span class="sh">"</span><span class="p">),</span> <span class="n">v</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="sh">"</span><span class="s">ascii</span><span class="sh">"</span><span class="p">)]</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="n">resp_headers</span><span class="p">.</span><span class="nf">items</span><span class="p">()</span>
        <span class="p">]</span>
        <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">asgi_send</span><span class="p">(</span>
            <span class="p">{</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">websocket.accept</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">subprotocol</span><span class="sh">"</span><span class="p">:</span> <span class="n">subprotocol</span><span class="p">,</span> <span class="sh">"</span><span class="s">headers</span><span class="sh">"</span><span class="p">:</span> <span class="n">headers</span><span class="p">}</span>
        <span class="p">)</span>
    <span class="bp">...</span>
</code></pre></div></div>
<p>While this method works as a way to accept the connection, it cannot be called immediately after we get the connection like how I want it to in the <a href="#goal"><em>goal</em></a> section, because we are not making sure it is called after the<code class="language-plaintext highlighter-rouge">websocket.connection</code> message is received.</p>

<p>To make sure we are ready to accept the connection, we need to know the status of the connection. One simple way to do it is by having a <code class="language-plaintext highlighter-rouge">ws_status</code> property on <code class="language-plaintext highlighter-rouge">Connection</code>, like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">WSStatus</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span>
    <span class="n">init</span> <span class="o">=</span> <span class="sh">"</span><span class="s">init</span><span class="sh">"</span>  <span class="c1"># got the scope, but haven't received any messages. The initial status
</span>    <span class="n">connecting</span> <span class="o">=</span> <span class="sh">"</span><span class="s">connecting</span><span class="sh">"</span>  <span class="c1"># got the "websocket.connect" message
</span>    <span class="n">accepted</span> <span class="o">=</span> <span class="sh">"</span><span class="s">accepted</span><span class="sh">"</span>  <span class="c1"># connection is accepted
</span>    <span class="n">closing</span> <span class="o">=</span> <span class="sh">"</span><span class="s">closing</span><span class="sh">"</span>  <span class="c1"># application initiated a close of connection.
</span>    <span class="n">finished</span> <span class="o">=</span> <span class="sh">"</span><span class="s">finished</span><span class="sh">"</span>  <span class="c1"># connection is closed
</span><span class="k">class</span> <span class="nc">Connection</span><span class="p">:</span>
    <span class="bp">...</span>
    <span class="n">ws_status</span> <span class="o">=</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">init</span>
</code></pre></div></div>
<p>Then, in order to set <code class="language-plaintext highlighter-rouge">ws_status</code> based on received ASGI messages, we can wrap <code class="language-plaintext highlighter-rouge">self.asgi_receive</code> in another method:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Connection</span><span class="p">:</span>
    <span class="bp">...</span>
    <span class="k">async</span> <span class="k">def</span> <span class="nf">_ws_receive</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">==</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">connecting</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">RuntimeError</span><span class="p">(</span>
                <span class="sh">"</span><span class="s">Unable to receive. Expect to accept or reject connection.</span><span class="sh">"</span>
            <span class="p">)</span>
        <span class="n">message</span> <span class="o">=</span> <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">asgi_receive</span><span class="p">()</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">==</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">init</span><span class="p">:</span>
            <span class="k">assert</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">]</span> <span class="o">==</span> <span class="sh">"</span><span class="s">websocket.connect</span><span class="sh">"</span>
            <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">=</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">connecting</span>
        <span class="k">if</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">]</span> <span class="o">==</span> <span class="sh">"</span><span class="s">websocket.disconnect</span><span class="sh">"</span><span class="p">:</span>
            <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">=</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">finished</span>
            <span class="n">self</span><span class="p">.</span><span class="n">ws_close_code</span> <span class="o">=</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">code</span><span class="sh">"</span><span class="p">]</span>
            <span class="n">self</span><span class="p">.</span><span class="n">finished</span> <span class="o">=</span> <span class="bp">True</span>
        <span class="k">return</span> <span class="n">message</span>
</code></pre></div></div>
<p>Finally, back in the <code class="language-plaintext highlighter-rouge">accept</code> method, we can use a simple <code class="language-plaintext highlighter-rouge">if</code> statement to make sure a connection is ready to be accepted before accepting it:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">async</span> <span class="k">def</span> <span class="nf">accept</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">subprotocol</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">==</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">init</span><span class="p">:</span>
            <span class="n">message</span> <span class="o">=</span> <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">_ws_receive</span><span class="p">()</span>
            <span class="k">assert</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">]</span> <span class="o">==</span> <span class="sh">"</span><span class="s">websocket.connect</span><span class="sh">"</span>
        <span class="bp">...</span>
</code></pre></div></div>
<p>To reject the connection, we can simply modify the <code class="language-plaintext highlighter-rouge">finish</code> method we have from previous post, just replace <code class="language-plaintext highlighter-rouge">riase NotImplementedError()</code> with these lines:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>       <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">asgi_send</span><span class="p">({</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">websocket.close</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">code</span><span class="sh">"</span><span class="p">:</span> <span class="n">close_code</span><span class="p">})</span>
       <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">=</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">closing</span>
       <span class="n">self</span><span class="p">.</span><span class="n">ws_close_code</span> <span class="o">=</span> <span class="n">close_code</span>
       <span class="n">self</span><span class="p">.</span><span class="n">finished</span> <span class="o">=</span> <span class="bp">True</span>
</code></pre></div></div>
<blockquote>
  <p>Note that rejecting a connection and closing a connection from the application is the same thing. The difference is if the <code class="language-plaintext highlighter-rouge">"websocket.close"</code> message is sent before or after a connection is accepted.</p>
</blockquote>

<h2 id="receive-and-send-data">Receive and Send Data</h2>

<p>As described in <a href="#goal">goal</a> section, I want to get received messages in an async generator. To do that, we can use a while loop to repeatedly receive ASGI messages, and <code class="language-plaintext highlighter-rouge">yield</code> the content of these messages when they are of type <code class="language-plaintext highlighter-rouge">"websocket.receive"</code> or stop the generator when <code class="language-plaintext highlighter-rouge">"websocket.disconnect"</code> is received.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">async</span> <span class="k">def</span> <span class="nf">iter_messages</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">!=</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">accepted</span><span class="p">:</span>
            <span class="k">return</span>
        <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
            <span class="n">message</span> <span class="o">=</span> <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">_ws_receive</span><span class="p">()</span>
            <span class="k">if</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">]</span> <span class="o">==</span> <span class="sh">"</span><span class="s">websocket.disconnect</span><span class="sh">"</span><span class="p">:</span>
                <span class="k">break</span>
            <span class="k">if</span> <span class="sh">"</span><span class="s">bytes</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">message</span> <span class="ow">and</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">bytes</span><span class="sh">"</span><span class="p">]</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
                <span class="n">data</span> <span class="o">=</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">bytes</span><span class="sh">"</span><span class="p">]</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">data</span> <span class="o">=</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">text</span><span class="sh">"</span><span class="p">]</span>
            <span class="k">yield</span> <span class="n">data</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">Connection.send</code> method can just be a thin wrapper around <code class="language-plaintext highlighter-rouge">self.asgi_send</code>, that sends messages of type <code class="language-plaintext highlighter-rouge">"websocket.send"</code>, and include either <code class="language-plaintext highlighter-rouge">text</code> or <code class="language-plaintext highlighter-rouge">bytes</code> depending on the type of messages.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">async</span> <span class="k">def</span> <span class="nf">send</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="n">Union</span><span class="p">[</span><span class="nb">bytes</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="sa">b</span><span class="sh">""</span><span class="p">,</span> <span class="n">finish</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="bp">False</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">finished</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">No message can be sent when connection closed</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
            <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">asgi_send</span><span class="p">({</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">websocket.send</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">text</span><span class="sh">"</span><span class="p">:</span> <span class="n">data</span><span class="p">})</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">asgi_send</span><span class="p">({</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">websocket.send</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">bytes</span><span class="sh">"</span><span class="p">:</span> <span class="n">data</span><span class="p">})</span>
</code></pre></div></div>

<h1 id="conclusion">Conclusion</h1>

<p>Now with the ability to accept/reject connections and send/receive messages, the WebSocket implementation is finished. The implementation is quite simple. Because WebSocket itself is simple, it is just a way for server and client to exchange information, what information should be exchanged and what meaning should it carry is up to the application to decide. And ASGI is already a fairly good abstraction.</p>

<p>To test WebSocket support, we start the server in <code class="language-plaintext highlighter-rouge">example-ws.py</code>, open browser and run the following javascript code in console:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">ws</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">WebSocket</span><span class="p">(</span><span class="dl">'</span><span class="s1">ws://localhost:8000/ws_echo</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">ws</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">message</span><span class="dl">"</span><span class="p">,(</span><span class="nx">msg</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">data</span><span class="p">));</span>
<span class="nx">ws</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">open</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">ws</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="dl">"</span><span class="s2">hi</span><span class="dl">"</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>
<p>You should be able to see “hi” printed back to the browser.</p>

<h2 id="clean-up">Clean up</h2>
<p>As a afterthought, I think it make sense for WebSocket connections to have its own class. Apart from making code easier to read, we can remove a lot of the <code class="language-plaintext highlighter-rouge">if self.type === ConnectionType.HTTP</code> statements, because when a connection is being created, we already know the connection type, and a connection would never change its type during throughout it’s whole life. To put it all together, here’s the code for the <code class="language-plaintext highlighter-rouge">WebSocketConnection</code> class:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">WebSocketConnection</span><span class="p">(</span><span class="n">Connection</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">scope</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">send</span><span class="p">,</span> <span class="n">receive</span><span class="p">):</span>
        <span class="nf">super</span><span class="p">().</span><span class="nf">__init__</span><span class="p">(</span><span class="n">scope</span><span class="p">,</span> <span class="n">send</span><span class="o">=</span><span class="n">send</span><span class="p">,</span> <span class="n">receive</span><span class="o">=</span><span class="n">receive</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">=</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">init</span>
        <span class="n">self</span><span class="p">.</span><span class="n">ws_close_code</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="n">self</span><span class="p">.</span><span class="n">started</span> <span class="o">=</span> <span class="bp">True</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">_ws_receive</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="n">message</span> <span class="o">=</span> <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">asgi_receive</span><span class="p">()</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">==</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">connecting</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">RuntimeError</span><span class="p">(</span>
                <span class="sh">"</span><span class="s">Unable to receive. Expect to accept or reject connection.</span><span class="sh">"</span>
            <span class="p">)</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">==</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">init</span><span class="p">:</span>
            <span class="k">assert</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">]</span> <span class="o">==</span> <span class="sh">"</span><span class="s">websocket.connect</span><span class="sh">"</span>
            <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">=</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">connecting</span>
        <span class="k">if</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">]</span> <span class="o">==</span> <span class="sh">"</span><span class="s">websocket.disconnect</span><span class="sh">"</span><span class="p">:</span>
            <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">=</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">finished</span>
            <span class="n">self</span><span class="p">.</span><span class="n">ws_close_code</span> <span class="o">=</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">code</span><span class="sh">"</span><span class="p">]</span>
            <span class="n">self</span><span class="p">.</span><span class="n">finished</span> <span class="o">=</span> <span class="bp">True</span>
        <span class="k">return</span> <span class="n">message</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">accept</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">subprotocol</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">==</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">init</span><span class="p">:</span>
            <span class="n">message</span> <span class="o">=</span> <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">_ws_receive</span><span class="p">()</span>
            <span class="k">assert</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">]</span> <span class="o">==</span> <span class="sh">"</span><span class="s">websocket.connect</span><span class="sh">"</span>
        <span class="n">headers</span> <span class="o">=</span> <span class="p">[</span>
            <span class="p">[</span><span class="n">k</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="sh">"</span><span class="s">ascii</span><span class="sh">"</span><span class="p">),</span> <span class="n">v</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="sh">"</span><span class="s">ascii</span><span class="sh">"</span><span class="p">)]</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="n">resp_headers</span><span class="p">.</span><span class="nf">items</span><span class="p">()</span>
        <span class="p">]</span>
        <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">asgi_send</span><span class="p">(</span>
            <span class="p">{</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">websocket.accept</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">subprotocol</span><span class="sh">"</span><span class="p">:</span> <span class="n">subprotocol</span><span class="p">,</span> <span class="sh">"</span><span class="s">headers</span><span class="sh">"</span><span class="p">:</span> <span class="n">headers</span><span class="p">}</span>
        <span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">=</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">accepted</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">send</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="n">Union</span><span class="p">[</span><span class="nb">bytes</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="sa">b</span><span class="sh">""</span><span class="p">,</span> <span class="n">finish</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="bp">False</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">finished</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">No message can be sent when connection closed</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
            <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">asgi_send</span><span class="p">({</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">websocket.send</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">text</span><span class="sh">"</span><span class="p">:</span> <span class="n">data</span><span class="p">})</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">asgi_send</span><span class="p">({</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">websocket.send</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">bytes</span><span class="sh">"</span><span class="p">:</span> <span class="n">data</span><span class="p">})</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">finish</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">close_code</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1000</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">finished</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">Connection already finished</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">asgi_send</span><span class="p">({</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">websocket.close</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">code</span><span class="sh">"</span><span class="p">:</span> <span class="n">close_code</span><span class="p">})</span>
        <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">=</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">closing</span>
        <span class="n">self</span><span class="p">.</span><span class="n">ws_close_code</span> <span class="o">=</span> <span class="n">close_code</span>
        <span class="n">self</span><span class="p">.</span><span class="n">finished</span> <span class="o">=</span> <span class="bp">True</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">iter_messages</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">ws_status</span> <span class="o">!=</span> <span class="n">WSStatus</span><span class="p">.</span><span class="n">accepted</span><span class="p">:</span>
            <span class="k">return</span>
        <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
            <span class="n">message</span> <span class="o">=</span> <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">_ws_receive</span><span class="p">()</span>
            <span class="k">if</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">]</span> <span class="o">==</span> <span class="sh">"</span><span class="s">websocket.disconnect</span><span class="sh">"</span><span class="p">:</span>
                <span class="k">break</span>
            <span class="k">if</span> <span class="sh">"</span><span class="s">bytes</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">message</span> <span class="ow">and</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">bytes</span><span class="sh">"</span><span class="p">]</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
                <span class="n">data</span> <span class="o">=</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">bytes</span><span class="sh">"</span><span class="p">]</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">data</span> <span class="o">=</span> <span class="n">message</span><span class="p">[</span><span class="sh">"</span><span class="s">text</span><span class="sh">"</span><span class="p">]</span>
            <span class="k">yield</span> <span class="n">data</span>

</code></pre></div></div>
<p>Now to instantiate connection with the correct class in <code class="language-plaintext highlighter-rouge">__init__.py</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="bp">...</span>
        <span class="n">conn_class</span> <span class="o">=</span> <span class="n">Connection</span>
        <span class="nf">if </span><span class="p">(</span><span class="n">scope</span><span class="p">[</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">]</span> <span class="o">==</span> <span class="sh">"</span><span class="s">websocket</span><span class="sh">"</span><span class="p">):</span>
            <span class="n">conn_class</span> <span class="o">=</span> <span class="n">WebSocketConnection</span>
        <span class="n">conn</span> <span class="o">=</span> <span class="nf">conn_class</span><span class="p">(</span><span class="n">scope</span><span class="p">,</span> <span class="n">send</span><span class="o">=</span><span class="n">send</span><span class="p">,</span> <span class="n">receive</span><span class="o">=</span><span class="n">receive</span><span class="p">)</span>
<span class="bp">...</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="ASGI" /><summary type="html"><![CDATA[Part 2 of building an ASGI framework - WebSocket]]></summary></entry><entry><title type="html">ASGI from scratch - Let’s build an ASGI web framework</title><link href="/2020/06/20/asgi-from-scratch.html" rel="alternate" type="text/html" title="ASGI from scratch - Let’s build an ASGI web framework" /><published>2020-06-20T22:15:00-04:00</published><updated>2020-06-20T22:15:00-04:00</updated><id>/2020/06/20/asgi-from-scratch</id><content type="html" xml:base="/2020/06/20/asgi-from-scratch.html"><![CDATA[<h1 id="intro">Intro</h1>
<p>The first time I used <a href="https://asgi.readthedocs.io/">ASGI</a>(<em>Asynchronous Server Gateway Interface</em>) was through <a href="https://github.com/django/channels">Channels</a> 1.0 when ASGI spec was still a draft. It was my first interview project which helped me get my current job at <a href="https://fellow.app">Fellow</a>. It felt magical at that time how easy it is to add WebSocket functionality to my Django app and handles authentication and other Django related things for me seamlessly.</p>

<p>ASGI specification is now at version 3 at the time of writing and both ASGI and Channels became part of Django Software Foundation. Compared to the draft version, it has matured a lot with added lifecycle calls and better application format, etc. Most excitingly, a healthy and fast-growing community is forming and we are seeing more and more ASGI servers running in production environments. At my company, we are serving a few million requests per day through ASGI running on <a href="https://github.com/django/daphne">Daphne</a>, Netflix’s <a href="https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072">Dispatch</a> is based on <a href="https://fastapi.tiangolo.com/">FastAPI</a>, a popular ASGI web application framework, and apparently, Microsoft is <a href="https://github.com/tiangolo/fastapi/pull/26">using it</a> too.</p>

<p>I would humbly advise anyone building web services in Python to learn about ASGI. And the best way to learn something is to built things with it, so in this blog post, I’ll walk through the steps to build a micro web application framework that speaks ASGI. I hope it can help explain how ASGI works.</p>

<h1 id="settings-the-goal">Settings the goal</h1>
<p>Before writing the first line of code, we need to have a basic understanding of what ASGI is and what we are building towards.</p>
<h2 id="how-asgi-works">How ASGI works</h2>
<p>Here’s a simple diagram showing how ASGI works at a high level.</p>
<pre><code class="language-mermaid">graph TD
	A[Client] --&gt;|HTTP, WebSocket, ...| B(ASGI Server)
	B --&gt; |scope, send, receive| C(ASGI application)
</code></pre>
<p>To put it in simple words, A browser(client), establishes a connection to ASGI server with a certain type of request (HTTP or WebSocket), the ASGI server then calls ASGI  application with information about the connection, encapsulated in a python dictionary called <code class="language-plaintext highlighter-rouge">scope</code>, and two callbacks, named <code class="language-plaintext highlighter-rouge">send</code> and <code class="language-plaintext highlighter-rouge">receive</code>, that the application can use to send and receive messages between server and client.</p>

<p>Here’s an example HTTP request scope</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
    <span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">http</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">http_version</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">1.1</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">server</span><span class="sh">"</span><span class="p">:</span> <span class="p">(</span><span class="sh">"</span><span class="s">127.0.0.1</span><span class="sh">"</span><span class="p">,</span> <span class="mi">8000</span><span class="p">),</span>
    <span class="sh">"</span><span class="s">client</span><span class="sh">"</span><span class="p">:</span> <span class="p">(</span><span class="sh">"</span><span class="s">127.0.0.1</span><span class="sh">"</span><span class="p">,</span> <span class="mi">60457</span><span class="p">),</span>
    <span class="sh">"</span><span class="s">scheme</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">http</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">method</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">GET</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">root_path</span><span class="sh">"</span><span class="p">:</span> <span class="sh">""</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">path</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">/hello/a</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">raw_path</span><span class="sh">"</span><span class="p">:</span> <span class="sa">b</span><span class="sh">"</span><span class="s">/hello/a</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">query_string</span><span class="sh">"</span><span class="p">:</span> <span class="sa">b</span><span class="sh">""</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">headers</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span>
        <span class="p">(</span><span class="sa">b</span><span class="sh">"</span><span class="s">host</span><span class="sh">"</span><span class="p">,</span> <span class="sa">b</span><span class="sh">"</span><span class="s">localhost:8000</span><span class="sh">"</span><span class="p">),</span>
        <span class="p">(</span><span class="sa">b</span><span class="sh">"</span><span class="s">connection</span><span class="sh">"</span><span class="p">,</span> <span class="sa">b</span><span class="sh">"</span><span class="s">keep-alive</span><span class="sh">"</span><span class="p">),</span>
        <span class="p">(</span>
            <span class="sa">b</span><span class="sh">"</span><span class="s">user-agent</span><span class="sh">"</span><span class="p">,</span>
            <span class="sa">b</span><span class="sh">"</span><span class="s">Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36</span><span class="sh">"</span><span class="p">,</span>
        <span class="p">),</span>
        <span class="p">(</span>
            <span class="sa">b</span><span class="sh">"</span><span class="s">accept</span><span class="sh">"</span><span class="p">,</span>
            <span class="sa">b</span><span class="sh">"</span><span class="s">text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9</span><span class="sh">"</span><span class="p">,</span>
        <span class="p">),</span>
        <span class="p">(</span><span class="sa">b</span><span class="sh">"</span><span class="s">accept-encoding</span><span class="sh">"</span><span class="p">,</span> <span class="sa">b</span><span class="sh">"</span><span class="s">gzip, deflate, br</span><span class="sh">"</span><span class="p">),</span>
        <span class="p">(</span><span class="sa">b</span><span class="sh">"</span><span class="s">accept-language</span><span class="sh">"</span><span class="p">,</span> <span class="sa">b</span><span class="sh">"</span><span class="s">en-US,en;q=0.9</span><span class="sh">"</span><span class="p">),</span>
        <span class="p">(</span>
            <span class="sa">b</span><span class="sh">"</span><span class="s">cookie</span><span class="sh">"</span><span class="p">,</span>
            <span class="sa">b</span><span class="sh">'</span><span class="s">csrftoken=dDA2IAPrvgPc7hkyBSyctxDk78KmhHAzUqR0LUpjXI3Xgki0QrGEWazE3RGZuLGl</span><span class="sh">'</span><span class="p">,</span>
        <span class="p">),</span>
    <span class="p">],</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You might notice that <code class="language-plaintext highlighter-rouge">scope</code> is not too different from a WSGI <code class="language-plaintext highlighter-rouge">environ</code>. In fact, ASGI interface is very similar to WSGI interface, but instead of getting a <code class="language-plaintext highlighter-rouge">environ</code> and <code class="language-plaintext highlighter-rouge">start_response</code> to send headers and using the return value of WSGI application as the response body, ASGI interfaces with the connection and allows us to receive and send messages multiple times during the lifecycle of the connection <strong>asynchronously</strong> until the connection is closed.  This allows a nice interface for both WebSocket and HTTP.</p>

<p>It’s also totally possible to wrap a WSGI application inside an ASGI application, just prepare a WSGI <code class="language-plaintext highlighter-rouge">environ</code> and <code class="language-plaintext highlighter-rouge">start_response</code> based on <code class="language-plaintext highlighter-rouge">scope</code>, <code class="language-plaintext highlighter-rouge">receive</code>, and <code class="language-plaintext highlighter-rouge">send</code> then call the WSGI application and it would work. If you delegate that call into a thread pool or something similar, you just made your WSGI application asynchronous. This is roughly how Channels wraps around Django.</p>

<h2 id="define-asgi-framework">Define ASGI framework</h2>
<p>When I say ASGI framework I refer it as a framework that makes building ASGI application easier and this does not include the ASGI server part. I’m mentioning this because some of the earlier Python asynchronous web frameworks have their own server implementation that also takes over tasks such as parsing  HTTP requests, handles network connections, etc. We are not doing those in ASGI web framework. As a spiritual successor to WSGI, where web servers, such as Gunicorn and uwsgi, and web frameworks, such as Flask and Django, are separated, ASGI has this separation too.</p>

<p>So, what does an ASGI application look like?</p>

<h3 id="asgi-hello-world">ASGI Hello World</h3>
<p>A simple ASGI hello world application can be written as:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">scope</span><span class="p">,</span> <span class="n">receive</span><span class="p">,</span> <span class="n">send</span><span class="p">):</span>
    <span class="n">name</span> <span class="o">=</span> <span class="n">scope</span><span class="p">[</span><span class="sh">"</span><span class="s">path</span><span class="sh">"</span><span class="p">].</span><span class="nf">split</span><span class="p">(</span><span class="sh">"</span><span class="s">/</span><span class="sh">"</span><span class="p">,</span> <span class="mi">1</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="ow">or</span> <span class="sh">"</span><span class="s">world</span><span class="sh">"</span>
    <span class="k">await</span> <span class="nf">send</span><span class="p">(</span>
        <span class="p">{</span>
            <span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">http.response.start</span><span class="sh">"</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">status</span><span class="sh">"</span><span class="p">:</span> <span class="mi">200</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">headers</span><span class="sh">"</span><span class="p">:</span> <span class="p">[[</span><span class="sa">b</span><span class="sh">"</span><span class="s">content-type</span><span class="sh">"</span><span class="p">,</span> <span class="sa">b</span><span class="sh">"</span><span class="s">text/plain</span><span class="sh">"</span><span class="p">],],</span>
        <span class="p">}</span>
    <span class="p">)</span>
    <span class="k">await</span> <span class="nf">send</span><span class="p">(</span>
        <span class="p">{</span>
            <span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">http.response.body</span><span class="sh">"</span><span class="p">,</span>
            <span class="sh">"</span><span class="s">body</span><span class="sh">"</span><span class="p">:</span> <span class="sa">f</span><span class="sh">"</span><span class="s">Hello, </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s">!</span><span class="sh">"</span><span class="p">.</span><span class="nf">encode</span><span class="p">(),</span>
            <span class="sh">"</span><span class="s">more_body</span><span class="sh">"</span><span class="p">:</span> <span class="bp">False</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">)</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">http.response.start</code> starts an HTTP response sending status code and response headers. In this example, it responds with the 200 OK status code and  has <code class="language-plaintext highlighter-rouge">content-type</code> set to <code class="language-plaintext highlighter-rouge">text/plain</code> in the headers.  <code class="language-plaintext highlighter-rouge">http.response.body</code> sends the response body, the <code class="language-plaintext highlighter-rouge">more_body</code> key tells the server if the response is finished. ASGI server might use this to know if a connection should be closed or automatically decide between a <code class="language-plaintext highlighter-rouge">content-length</code> header or a chunked encoding.</p>

<p>We can run the application with <a href="https://www.uvicorn.org/">uvicorn</a>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>uvicorn asgi-hello:application
</code></pre></div></div>
<p>And you should be able to visit <code class="language-plaintext highlighter-rouge">http://localhost:8000/</code> and get <code class="language-plaintext highlighter-rouge">Hello, world</code>.Visiting <code class="language-plaintext highlighter-rouge">http://localhost:8000/tom</code> would get you <code class="language-plaintext highlighter-rouge">Hello, tom</code>.</p>

<blockquote>
  <p>By the way, uvicorn is pretty fast, a simple benchmark with <code class="language-plaintext highlighter-rouge">wrk -d10s http://localhost:8000/hi</code> on a 2018 lowest spec MacBook Air yields <code class="language-plaintext highlighter-rouge">Requests/sec:  27857.87</code>.</p>
</blockquote>

<p>Although this approach works with a simple hello world example, it’s not exactly convenient to write a more complex application this way. For one, it doesn’t do routing, if you want to respond differently for different paths, you’ll probably end up with a huge  <code class="language-plaintext highlighter-rouge">if ... else if ... else</code> clause. Secondly, having to write the ASGI message every time in the form of a python dict is quite arduous. Third, in a complex application, it gets harder to track the status of the connection, such as is the response started, is the response ended, should I start the response here, etc.</p>

<h3 id="goal">Goal</h3>
<p>With the new framework, I hope to be able to write an ASGI application like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">asyncio</span>
<span class="kn">from</span> <span class="n">aaf</span> <span class="kn">import</span> <span class="n">aaf</span> <span class="c1"># Another ASGI framework
</span><span class="kn">from</span> <span class="n">aaf.routing</span> <span class="kn">import</span> <span class="n">Router</span>
<span class="kn">from</span> <span class="n">aaf.response</span> <span class="kn">import</span> <span class="n">HttpResponse</span>

<span class="n">router</span> <span class="o">=</span> <span class="nc">Router</span><span class="p">()</span>

<span class="nd">@router.route</span><span class="p">(</span><span class="sh">'</span><span class="s">/</span><span class="sh">'</span><span class="p">)</span>
<span class="nd">@router.route</span><span class="p">(</span><span class="sh">'</span><span class="s">/&lt;name&gt;</span><span class="sh">'</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">hello</span><span class="p">(</span><span class="n">connection</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="sh">'</span><span class="s">world</span><span class="sh">'</span><span class="p">):</span>
	<span class="k">return</span> <span class="nc">HttpResponse</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Hello, </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>


<span class="nd">@router.route</span><span class="p">(</span><span class="sh">'</span><span class="s">/count</span><span class="sh">'</span><span class="p">)</span>
<span class="nd">@router.route</span><span class="p">(</span><span class="sh">'</span><span class="s">/count/&lt;int:number&gt;</span><span class="sh">'</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">count</span><span class="p">(</span><span class="n">connection</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">10</span><span class="p">):</span>
	<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">number</span><span class="p">):</span>
		<span class="k">await</span> <span class="n">connection</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="s">count </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="se">\n</span><span class="sh">'</span><span class="p">,</span> <span class="n">finish</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
		<span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="nf">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
	<span class="k">await</span> <span class="n">connection</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="sh">''</span><span class="p">,</span> <span class="n">finish</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>


<span class="nd">@router.route</span><span class="p">(</span><span class="sh">'</span><span class="s">/echo</span><span class="sh">'</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">echo</span><span class="p">(</span><span class="n">connection</span><span class="p">):</span>
	<span class="n">body</span> <span class="o">=</span> <span class="k">await</span> <span class="n">connection</span><span class="p">.</span><span class="nf">body</span><span class="p">()</span>
	<span class="k">await</span> <span class="n">connection</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="n">finish</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>


<span class="n">app</span> <span class="o">=</span> <span class="nf">aaf</span><span class="p">([</span><span class="n">router</span><span class="p">])</span>
</code></pre></div></div>
<p>I hope this snippet of how I want the framework to look like is self-explanatory. But here are some of the key things I want to achieve:</p>
<ol>
  <li>It should be able to handle HTTP response declaratively and imperatively.</li>
  <li>It should support Flask style routing with parameter parsing.</li>
</ol>

<h1 id="building-the-framework">Building the framework</h1>
<h2 id="connection-class">Connection class</h2>
<p>The <code class="language-plaintext highlighter-rouge">Connection</code> class will represent an ASGI HTTP or WebSocket connection. It’s a class that encapsulates the three basic elements in ASGI, namely <code class="language-plaintext highlighter-rouge">scope</code>, <code class="language-plaintext highlighter-rouge">send</code> and <code class="language-plaintext highlighter-rouge">receive</code>, and expose some convenient methods and properties so that users don’t need to verbosely write out all the ASGI messages and parse everything, such as cookies and headers, from <code class="language-plaintext highlighter-rouge">scope</code>. But it should allow users to access the original <code class="language-plaintext highlighter-rouge">scope</code>, <code class="language-plaintext highlighter-rouge">send</code> and <code class="language-plaintext highlighter-rouge">receive</code> when they want to, so that the composability of ASGI applications is maintained. For example, it should allow user to delegate certain <code class="language-plaintext highlighter-rouge">connection</code>s to another ASGI application by calling <code class="language-plaintext highlighter-rouge">another_asgi_app(connection.scope, connectionn.asgi_send, connection.asgi_receive)</code>.</p>

<p>Here’s a simple implementation of the <code class="language-plaintext highlighter-rouge">Connection</code> class.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">enum</span> <span class="kn">import</span> <span class="n">Enum</span>
<span class="kn">from</span> <span class="n">functools</span> <span class="kn">import</span> <span class="n">cached_property</span>
<span class="kn">from</span> <span class="n">http.cookies</span> <span class="kn">import</span> <span class="n">SimpleCookie</span>
<span class="kn">from</span> <span class="n">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Awaitable</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Union</span>
<span class="kn">from</span> <span class="n">urllib.parse</span> <span class="kn">import</span> <span class="n">parse_qsl</span><span class="p">,</span> <span class="n">unquote_plus</span>

<span class="kn">from</span> <span class="n">werkzeug.datastructures</span> <span class="kn">import</span> <span class="n">Headers</span><span class="p">,</span> <span class="n">MultiDict</span>

<span class="n">CoroutineFunction</span> <span class="o">=</span> <span class="n">Callable</span><span class="p">[[</span><span class="n">Any</span><span class="p">],</span> <span class="n">Awaitable</span><span class="p">]</span>


<span class="k">class</span> <span class="nc">ConnectionType</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span>
    <span class="n">HTTP</span> <span class="o">=</span> <span class="sh">"</span><span class="s">HTTP</span><span class="sh">"</span>
    <span class="n">WebSocket</span> <span class="o">=</span> <span class="sh">"</span><span class="s">WebSocket</span><span class="sh">"</span>


<span class="k">class</span> <span class="nc">Connection</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span>
        <span class="n">self</span><span class="p">,</span> <span class="n">scope</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">send</span><span class="p">:</span> <span class="n">CoroutineFunction</span><span class="p">,</span> <span class="n">receive</span><span class="p">:</span> <span class="n">CoroutineFunction</span>
    <span class="p">):</span>
        <span class="n">self</span><span class="p">.</span><span class="n">scope</span> <span class="o">=</span> <span class="n">scope</span>
        <span class="n">self</span><span class="p">.</span><span class="n">asgi_send</span> <span class="o">=</span> <span class="n">send</span>
        <span class="n">self</span><span class="p">.</span><span class="n">asgi_receive</span> <span class="o">=</span> <span class="n">receive</span>

        <span class="n">self</span><span class="p">.</span><span class="n">started</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="n">self</span><span class="p">.</span><span class="n">finished</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="n">self</span><span class="p">.</span><span class="n">resp_headers</span> <span class="o">=</span> <span class="nc">Headers</span><span class="p">()</span>
        <span class="n">self</span><span class="p">.</span><span class="n">resp_cookies</span><span class="p">:</span> <span class="n">SimpleCookie</span> <span class="o">=</span> <span class="nc">SimpleCookie</span><span class="p">()</span>
        <span class="n">self</span><span class="p">.</span><span class="n">resp_status_code</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="bp">None</span>

        <span class="n">self</span><span class="p">.</span><span class="n">http_body</span> <span class="o">=</span> <span class="sa">b</span><span class="sh">""</span>
        <span class="n">self</span><span class="p">.</span><span class="n">http_has_more_body</span> <span class="o">=</span> <span class="bp">True</span>
        <span class="n">self</span><span class="p">.</span><span class="n">http_received_body_length</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="nd">@cached_property</span>
    <span class="k">def</span> <span class="nf">req_headers</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Headers</span><span class="p">:</span>
        <span class="n">headers</span> <span class="o">=</span> <span class="nc">Headers</span><span class="p">()</span>
        <span class="nf">for </span><span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="p">)</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="n">scope</span><span class="p">[</span><span class="sh">"</span><span class="s">headers</span><span class="sh">"</span><span class="p">]:</span>
            <span class="n">headers</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">k</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="sh">"</span><span class="s">ascii</span><span class="sh">"</span><span class="p">),</span> <span class="n">v</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="sh">"</span><span class="s">ascii</span><span class="sh">"</span><span class="p">))</span>
        <span class="k">return</span> <span class="n">headers</span>

    <span class="nd">@cached_property</span>
    <span class="k">def</span> <span class="nf">req_cookies</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">SimpleCookie</span><span class="p">:</span>
        <span class="n">cookie</span> <span class="o">=</span> <span class="nc">SimpleCookie</span><span class="p">()</span>
        <span class="n">cookie</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">req_headers</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">cookie</span><span class="sh">"</span><span class="p">,</span> <span class="p">{}))</span>
        <span class="k">return</span> <span class="n">cookie</span>

    <span class="nd">@cached_property</span>
    <span class="k">def</span> <span class="nf">type</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ConnectionType</span><span class="p">:</span>
        <span class="nf">return </span><span class="p">(</span>
            <span class="n">ConnectionType</span><span class="p">.</span><span class="n">WebSocket</span>
            <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">scope</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">)</span> <span class="o">==</span> <span class="sh">"</span><span class="s">websocket</span><span class="sh">"</span>
            <span class="k">else</span> <span class="n">ConnectionType</span><span class="p">.</span><span class="n">HTTP</span>
        <span class="p">)</span>

    <span class="nd">@cached_property</span>
    <span class="k">def</span> <span class="nf">method</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">scope</span><span class="p">[</span><span class="sh">"</span><span class="s">method</span><span class="sh">"</span><span class="p">]</span>

    <span class="nd">@cached_property</span>
    <span class="k">def</span> <span class="nf">path</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">scope</span><span class="p">[</span><span class="sh">"</span><span class="s">path</span><span class="sh">"</span><span class="p">]</span>

    <span class="nd">@cached_property</span>
    <span class="k">def</span> <span class="nf">query</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">MultiDict</span><span class="p">:</span>
        <span class="k">return</span> <span class="nc">MultiDict</span><span class="p">(</span><span class="nf">parse_qsl</span><span class="p">(</span><span class="nf">unquote_plus</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">scope</span><span class="p">[</span><span class="sh">"</span><span class="s">query_string</span><span class="sh">"</span><span class="p">].</span><span class="nf">decode</span><span class="p">())))</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">send</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="n">Union</span><span class="p">[</span><span class="nb">bytes</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="sa">b</span><span class="sh">""</span><span class="p">,</span> <span class="n">finish</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="bp">False</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">finished</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">No message can be sent when connection closed</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="nb">type</span> <span class="o">==</span> <span class="n">ConnectionType</span><span class="p">.</span><span class="n">HTTP</span><span class="p">:</span>
            <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
                <span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="nf">encode</span><span class="p">()</span>
            <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">_http_send</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">finish</span><span class="o">=</span><span class="n">finish</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">NotImplementedError</span><span class="p">()</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">_http_send</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">bytes</span> <span class="o">=</span> <span class="sa">b</span><span class="sh">""</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">finish</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="bp">False</span><span class="p">):</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">self</span><span class="p">.</span><span class="n">started</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">finish</span><span class="p">:</span>
                <span class="n">self</span><span class="p">.</span><span class="nf">put_resp_header</span><span class="p">(</span><span class="sh">"</span><span class="s">content-length</span><span class="sh">"</span><span class="p">,</span> <span class="nf">str</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">data</span><span class="p">)))</span>
            <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">start_resp</span><span class="p">()</span>
        <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">asgi_send</span><span class="p">(</span>
            <span class="p">{</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">http.response.body</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">body</span><span class="sh">"</span><span class="p">:</span> <span class="n">data</span> <span class="ow">or</span> <span class="sa">b</span><span class="sh">""</span><span class="p">,</span> <span class="sh">"</span><span class="s">more_body</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}</span>
        <span class="p">)</span>
        <span class="k">if</span> <span class="n">finish</span><span class="p">:</span>
            <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">finish</span><span class="p">()</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">finish</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">close_code</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1000</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="nb">type</span> <span class="o">==</span> <span class="n">ConnectionType</span><span class="p">.</span><span class="n">HTTP</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">finished</span><span class="p">:</span>
                <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">Connection already finished</span><span class="sh">"</span><span class="p">)</span>
            <span class="k">if</span> <span class="ow">not</span> <span class="n">self</span><span class="p">.</span><span class="n">started</span><span class="p">:</span>
                <span class="n">self</span><span class="p">.</span><span class="n">resp_status_code</span> <span class="o">=</span> <span class="mi">204</span>
                <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">start_resp</span><span class="p">()</span>
            <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">asgi_send</span><span class="p">(</span>
                <span class="p">{</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">http.response.body</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">body</span><span class="sh">"</span><span class="p">:</span> <span class="sa">b</span><span class="sh">""</span><span class="p">,</span> <span class="sh">"</span><span class="s">more_body</span><span class="sh">"</span><span class="p">:</span> <span class="bp">False</span><span class="p">}</span>
            <span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">NotImplementedError</span><span class="p">()</span>
            <span class="c1"># await self.asgi_send({"type": "websocket.close", "code": close_code})
</span>        <span class="n">self</span><span class="p">.</span><span class="n">finished</span> <span class="o">=</span> <span class="bp">True</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">start_resp</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">started</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">resp already started</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">self</span><span class="p">.</span><span class="n">resp_status_code</span><span class="p">:</span>
            <span class="n">self</span><span class="p">.</span><span class="n">resp_status_code</span> <span class="o">=</span> <span class="mi">200</span>
        <span class="n">headers</span> <span class="o">=</span> <span class="p">[</span>
            <span class="p">[</span><span class="n">k</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="sh">"</span><span class="s">ascii</span><span class="sh">"</span><span class="p">),</span> <span class="n">v</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="sh">"</span><span class="s">ascii</span><span class="sh">"</span><span class="p">)]</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="n">resp_headers</span><span class="p">.</span><span class="nf">items</span><span class="p">()</span>
        <span class="p">]</span>
        <span class="k">for</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="n">resp_cookies</span><span class="p">.</span><span class="nf">values</span><span class="p">():</span>
            <span class="n">headers</span><span class="p">.</span><span class="nf">append</span><span class="p">([</span><span class="sa">b</span><span class="sh">"</span><span class="s">Set-Cookie</span><span class="sh">"</span><span class="p">,</span> <span class="n">value</span><span class="p">.</span><span class="nc">OutputString</span><span class="p">().</span><span class="nf">encode</span><span class="p">(</span><span class="sh">"</span><span class="s">ascii</span><span class="sh">"</span><span class="p">)])</span>
        <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">asgi_send</span><span class="p">(</span>
            <span class="p">{</span>
                <span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">http.response.start</span><span class="sh">"</span><span class="p">,</span>
                <span class="sh">"</span><span class="s">status</span><span class="sh">"</span><span class="p">:</span> <span class="n">self</span><span class="p">.</span><span class="n">resp_status_code</span><span class="p">,</span>
                <span class="sh">"</span><span class="s">headers</span><span class="sh">"</span><span class="p">:</span> <span class="n">headers</span><span class="p">,</span>
            <span class="p">}</span>
        <span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">started</span> <span class="o">=</span> <span class="bp">True</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">body_iter</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="nb">type</span> <span class="o">!=</span> <span class="n">ConnectionType</span><span class="p">.</span><span class="n">HTTP</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">connection type is not HTTP</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">http_received_body_length</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">self</span><span class="p">.</span><span class="n">http_has_more_body</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">body iter is already started and is not finished</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">http_received_body_length</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">self</span><span class="p">.</span><span class="n">http_has_more_body</span><span class="p">:</span>
            <span class="k">yield</span> <span class="n">self</span><span class="p">.</span><span class="n">http_body</span>
        <span class="n">req_body_length</span> <span class="o">=</span> <span class="p">(</span>
            <span class="nf">int</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">req_headers</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">content-length</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">0</span><span class="sh">"</span><span class="p">))</span>
            <span class="k">if</span> <span class="ow">not</span> <span class="n">self</span><span class="p">.</span><span class="n">req_headers</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">transfer-encoding</span><span class="sh">"</span><span class="p">)</span> <span class="o">==</span> <span class="sh">"</span><span class="s">chunked</span><span class="sh">"</span>
            <span class="k">else</span> <span class="bp">None</span>
        <span class="p">)</span>
        <span class="k">while</span> <span class="n">self</span><span class="p">.</span><span class="n">http_has_more_body</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">req_body_length</span> <span class="ow">and</span> <span class="n">self</span><span class="p">.</span><span class="n">http_received_body_length</span> <span class="o">&gt;</span> <span class="n">req_body_length</span><span class="p">:</span>
                <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">body is longer than declared</span><span class="sh">"</span><span class="p">)</span>
            <span class="n">message</span> <span class="o">=</span> <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">asgi_receive</span><span class="p">()</span>
            <span class="n">message_type</span> <span class="o">=</span> <span class="n">message</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">message</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">)</span> <span class="o">==</span> <span class="sh">"</span><span class="s">http.disconnect</span><span class="sh">"</span><span class="p">:</span>
                <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">Disconnected</span><span class="sh">"</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">message_type</span> <span class="o">!=</span> <span class="sh">"</span><span class="s">http.request</span><span class="sh">"</span><span class="p">:</span>
                <span class="k">continue</span>
            <span class="n">chunk</span> <span class="o">=</span> <span class="n">message</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">body</span><span class="sh">"</span><span class="p">,</span> <span class="sa">b</span><span class="sh">""</span><span class="p">)</span>
            <span class="k">if</span> <span class="ow">not</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">chunk</span><span class="p">,</span> <span class="nb">bytes</span><span class="p">):</span>
                <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">Chunk is not bytes</span><span class="sh">"</span><span class="p">)</span>
            <span class="n">self</span><span class="p">.</span><span class="n">http_body</span> <span class="o">+=</span> <span class="n">chunk</span>
            <span class="n">self</span><span class="p">.</span><span class="n">http_has_more_body</span> <span class="o">=</span> <span class="n">message</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">more_body</span><span class="sh">"</span><span class="p">,</span> <span class="bp">False</span><span class="p">)</span> <span class="ow">or</span> <span class="bp">False</span>
            <span class="n">self</span><span class="p">.</span><span class="n">http_received_body_length</span> <span class="o">+=</span> <span class="nf">len</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
            <span class="k">yield</span> <span class="n">chunk</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">body</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="sa">b</span><span class="sh">""</span><span class="p">.</span><span class="nf">join</span><span class="p">([</span><span class="n">chunks</span> <span class="k">async</span> <span class="k">for</span> <span class="n">chunks</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="nf">body_iter</span><span class="p">()])</span>

    <span class="k">def</span> <span class="nf">put_resp_header</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
        <span class="n">self</span><span class="p">.</span><span class="n">resp_headers</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">put_resp_cookie</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
        <span class="n">self</span><span class="p">.</span><span class="n">resp_cookies</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
</code></pre></div></div>
<p>I hope this code is easy to read. All it does is providing us with an interface that makes it easier to access info about the request, read request body, make sure body length is not larger than declared, and send the response back to the client. It also provides some safeguards to ensure messages of the right type is sent in the right state. For example, it ensures that <code class="language-plaintext highlighter-rouge">http.response.start</code> messages are always sent before <code class="language-plaintext highlighter-rouge">http.response.body</code>, and no more messages are sent after connection closed. Most of the heavy lifting in parsing header, cookie, and query is done by <code class="language-plaintext highlighter-rouge">werkzeug</code> and other Python built-in superheroes.</p>

<p>Let’s make a simple ASGI application with the <code class="language-plaintext highlighter-rouge">Connection</code> class:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># example1.py
</span><span class="kn">from</span> <span class="n">aaf.connection</span> <span class="kn">import</span> <span class="n">Connection</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">app</span><span class="p">(</span><span class="n">scope</span><span class="p">,</span> <span class="n">receive</span><span class="p">,</span> <span class="n">send</span><span class="p">):</span>
    <span class="n">conn</span> <span class="o">=</span> <span class="nc">Connection</span><span class="p">(</span><span class="n">scope</span><span class="p">,</span> <span class="n">receive</span><span class="o">=</span><span class="n">receive</span><span class="p">,</span> <span class="n">send</span><span class="o">=</span><span class="n">send</span><span class="p">)</span>
    <span class="n">name</span> <span class="o">=</span> <span class="n">conn</span><span class="p">.</span><span class="n">query</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">name</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">await</span> <span class="n">conn</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="sh">"</span><span class="s">Hello, </span><span class="sh">"</span> <span class="o">+</span> <span class="p">(</span><span class="n">name</span> <span class="ow">or</span> <span class="sh">"</span><span class="s">world</span><span class="sh">"</span><span class="p">),</span> <span class="n">finish</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
</code></pre></div></div>
<p>We can run this example by executing <code class="language-plaintext highlighter-rouge">uvicorn example_1:app</code>. Requesting <code class="language-plaintext highlighter-rouge">/?name=foo</code> should correctly return <code class="language-plaintext highlighter-rouge">Hello, foo</code>.</p>

<p>We now have an ASGI application that’s shorter in code and does more (accessing HTTP query). That’s one step closer to what we want!</p>

<h2 id="http-responses">HTTP Responses</h2>
<p>It’s usually not required to have fine control over when to send what in an HTTP request-response cycle, returning a response that knows how to set headers and send the body is more convenient and familiar. To do that, we can write a simple <code class="language-plaintext highlighter-rouge">HttpResponse</code> helper class.</p>

<p>A <code class="language-plaintext highlighter-rouge">JsonResponse</code> is also added while we are at it.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># response.py
</span><span class="kn">import</span> <span class="n">json</span>
<span class="kn">from</span> <span class="n">typing</span> <span class="kn">import</span> <span class="n">Union</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Mapping</span><span class="p">,</span> <span class="n">Any</span>

<span class="kn">from</span> <span class="n">.connection</span> <span class="kn">import</span> <span class="n">Connection</span>


<span class="k">class</span> <span class="nc">HttpResponse</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span>
        <span class="n">self</span><span class="p">,</span>
        <span class="n">body</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Union</span><span class="p">[</span><span class="nb">bytes</span><span class="p">,</span> <span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="sa">b</span><span class="sh">""</span><span class="p">,</span>
        <span class="n">connection</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Connection</span><span class="p">]</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
        <span class="o">*</span><span class="p">,</span>
        <span class="n">status_code</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">200</span><span class="p">,</span>
        <span class="n">headers</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Mapping</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="p">):</span>
        <span class="n">self</span><span class="p">.</span><span class="n">body</span> <span class="o">=</span> <span class="n">body</span>
        <span class="n">self</span><span class="p">.</span><span class="n">connection</span> <span class="o">=</span> <span class="n">connection</span>
        <span class="n">self</span><span class="p">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="n">status_code</span>
        <span class="n">self</span><span class="p">.</span><span class="n">headers</span> <span class="o">=</span> <span class="n">headers</span>

    <span class="k">def</span> <span class="nf">__await__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">self</span><span class="p">.</span><span class="n">connection</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">No connection</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">connection</span><span class="p">.</span><span class="n">resp_status_code</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">status_code</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">headers</span><span class="p">:</span>
            <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="n">headers</span><span class="p">.</span><span class="nf">items</span><span class="p">():</span>
                <span class="n">self</span><span class="p">.</span><span class="n">connection</span><span class="p">.</span><span class="nf">put_resp_header</span><span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">connection</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">body</span><span class="p">,</span> <span class="n">finish</span><span class="o">=</span><span class="bp">True</span><span class="p">).</span><span class="nf">__await__</span><span class="p">()</span>


<span class="k">class</span> <span class="nc">JsonResponse</span><span class="p">(</span><span class="n">HttpResponse</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span>
        <span class="n">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">connection</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Connection</span><span class="p">]</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span>
    <span class="p">):</span>
        <span class="n">body</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="nf">dumps</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
        <span class="n">headers</span> <span class="o">=</span> <span class="n">kwargs</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">headers</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">headers</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
            <span class="n">headers</span> <span class="o">=</span> <span class="p">{}</span>
        <span class="n">headers</span><span class="p">[</span><span class="sh">"</span><span class="s">content-type</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="sh">"</span><span class="s">application/json</span><span class="sh">"</span>
        <span class="nf">super</span><span class="p">().</span><span class="nf">__init__</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="n">connection</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</code></pre></div></div>
<p>There’s not much going on in the <code class="language-plaintext highlighter-rouge">HttpResponse</code> class. All it does is providing a familiar interface allowing us to pass in a response body,  optional headers, optional status code, and calls the underneath methods in <code class="language-plaintext highlighter-rouge">Connection</code> class for us. In the example of <code class="language-plaintext highlighter-rouge">JsonResponse</code> class, it also sets the <code class="language-plaintext highlighter-rouge">content-type</code> header.</p>

<p>Let’s write another ASGI application to test it:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">app</span><span class="p">(</span><span class="n">scope</span><span class="p">,</span> <span class="n">receive</span><span class="p">,</span> <span class="n">send</span><span class="p">):</span>
    <span class="n">conn</span> <span class="o">=</span> <span class="nc">Connection</span><span class="p">(</span><span class="n">scope</span><span class="p">,</span> <span class="n">receive</span><span class="o">=</span><span class="n">receive</span><span class="p">,</span> <span class="n">send</span><span class="o">=</span><span class="n">send</span><span class="p">)</span>
    <span class="k">await</span> <span class="nc">JsonResponse</span><span class="p">(</span><span class="n">conn</span><span class="p">.</span><span class="n">query</span><span class="p">.</span><span class="nf">to_dict</span><span class="p">(</span><span class="n">flat</span><span class="o">=</span><span class="bp">False</span><span class="p">),</span> <span class="n">conn</span><span class="p">)</span>
</code></pre></div></div>
<p>This application should return all query parameters in the form of a JSON object when visited.</p>

<p>Great, the application is even shorter!</p>

<p>You might have noticed that this is not exactly how it’s used in the <a href="#goal"><em>Goal</em> </a> section, where we can just <code class="language-plaintext highlighter-rouge">return</code> the response object instead of <code class="language-plaintext highlighter-rouge">await</code> on it. This is because this example is a plain ASGI application and the one in the original <a href="#goal"><em>Goal</em> </a> section is in the context of a <code class="language-plaintext highlighter-rouge">Router</code>, who calls the <code class="language-plaintext highlighter-rouge">await</code> for us. The <code class="language-plaintext highlighter-rouge">connection</code> argument is allowed to be <code class="language-plaintext highlighter-rouge">None</code> in the constructor for the same reason.</p>

<h2 id="routing">Routing</h2>
<p>Router dispatches requests based on requested url and HTTP method to different handlers. Most router implementations also parses parameters in urls. For example, if we define a router</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@router.route</span><span class="p">(</span><span class="sh">'</span><span class="s">/a/&lt;param_a&gt;/&lt;param_b&gt;</span><span class="sh">'</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">handler</span><span class="p">(</span><span class="n">connection</span><span class="p">,</span> <span class="n">param_a</span><span class="p">,</span> <span class="n">param_b</span><span class="p">):</span>
	<span class="bp">...</span>
</code></pre></div></div>
<p>and then tell the router to match the URL <code class="language-plaintext highlighter-rouge">/a/foo/bar</code>, it should give us the <code class="language-plaintext highlighter-rouge">handler</code> function as well as the parameters <code class="language-plaintext highlighter-rouge">param_a</code> and <code class="language-plaintext highlighter-rouge">params_b</code>.</p>

<p>This is indeed not easy but luckily, <code class="language-plaintext highlighter-rouge">werkzeug</code> comes with a <code class="language-plaintext highlighter-rouge">routing</code> module with does exactly this and even more, such as automatically redirect in the case of missing trailing slash. With its help, we can implement our <code class="language-plaintext highlighter-rouge">routing</code> module in around 60 lines of code.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># routing.py
</span><span class="kn">import</span> <span class="n">functools</span>
<span class="kn">from</span> <span class="n">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Iterable</span><span class="p">,</span> <span class="n">Optional</span>

<span class="kn">from</span> <span class="n">werkzeug.routing</span> <span class="kn">import</span> <span class="n">Map</span><span class="p">,</span> <span class="n">MethodNotAllowed</span><span class="p">,</span> <span class="n">NotFound</span><span class="p">,</span> <span class="n">RequestRedirect</span><span class="p">,</span> <span class="n">Rule</span>

<span class="kn">from</span> <span class="n">.connection</span> <span class="kn">import</span> <span class="n">Connection</span>
<span class="kn">from</span> <span class="n">.response</span> <span class="kn">import</span> <span class="n">HttpResponse</span>


<span class="k">class</span> <span class="nc">Router</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="nf">super</span><span class="p">().</span><span class="nf">__init__</span><span class="p">()</span>
        <span class="n">self</span><span class="p">.</span><span class="n">url_map</span> <span class="o">=</span> <span class="nc">Map</span><span class="p">()</span>
        <span class="n">self</span><span class="p">.</span><span class="n">endpoint_to_handler</span> <span class="o">=</span> <span class="p">{}</span>

    <span class="k">def</span> <span class="nf">route</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">rule</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="n">methods</span> <span class="o">=</span> <span class="nf">set</span><span class="p">(</span><span class="n">methods</span><span class="p">)</span> <span class="k">if</span> <span class="n">methods</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span> <span class="k">else</span> <span class="bp">None</span>
        <span class="k">if</span> <span class="n">methods</span> <span class="ow">and</span> <span class="ow">not</span> <span class="sh">"</span><span class="s">OPTIONS</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">methods</span><span class="p">:</span>
            <span class="n">methods</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="sh">"</span><span class="s">OPTIONS</span><span class="sh">"</span><span class="p">)</span>

        <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span> <span class="n">handler</span><span class="p">:</span> <span class="n">Callable</span><span class="p">):</span>
            <span class="n">self</span><span class="p">.</span><span class="nf">add_route</span><span class="p">(</span>
                <span class="n">rule_string</span><span class="o">=</span><span class="n">rule</span><span class="p">,</span> <span class="n">handler</span><span class="o">=</span><span class="n">handler</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="n">methods</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">name</span>
            <span class="p">)</span>
            <span class="k">return</span> <span class="n">handler</span>

        <span class="k">return</span> <span class="n">functools</span><span class="p">.</span><span class="nf">partial</span><span class="p">(</span><span class="n">decorator</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">add_route</span><span class="p">(</span>
        <span class="n">self</span><span class="p">,</span>
        <span class="o">*</span><span class="p">,</span>
        <span class="n">rule_string</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
        <span class="n">handler</span><span class="p">:</span> <span class="n">Callable</span><span class="p">,</span>
        <span class="n">name</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
        <span class="n">methods</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
    <span class="p">):</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">name</span><span class="p">:</span>
            <span class="n">name</span> <span class="o">=</span> <span class="n">handler</span><span class="p">.</span><span class="n">__name__</span>
        <span class="n">existing_handler</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">endpoint_to_handler</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">existing_handler</span> <span class="ow">and</span> <span class="n">existing_handler</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">handler</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">Duplicated route name: %s</span><span class="sh">"</span> <span class="o">%</span> <span class="p">(</span><span class="n">name</span><span class="p">))</span>
        <span class="n">self</span><span class="p">.</span><span class="n">url_map</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">Rule</span><span class="p">(</span><span class="n">rule_string</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="n">name</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="n">methods</span><span class="p">))</span>
        <span class="n">self</span><span class="p">.</span><span class="n">endpoint_to_handler</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">handler</span>

    <span class="k">def</span> <span class="nf">get_url_binding_for_connection</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">connection</span><span class="p">:</span> <span class="n">Connection</span><span class="p">):</span>
        <span class="n">scope</span> <span class="o">=</span> <span class="n">connection</span><span class="p">.</span><span class="n">scope</span>
        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">url_map</span><span class="p">.</span><span class="nf">bind</span><span class="p">(</span>
            <span class="n">connection</span><span class="p">.</span><span class="n">req_headers</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">host</span><span class="sh">"</span><span class="p">),</span>
            <span class="n">path_info</span><span class="o">=</span><span class="n">scope</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">path</span><span class="sh">"</span><span class="p">),</span>
            <span class="n">script_name</span><span class="o">=</span><span class="n">scope</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">root_path</span><span class="sh">"</span><span class="p">)</span> <span class="ow">or</span> <span class="bp">None</span><span class="p">,</span>
            <span class="n">url_scheme</span><span class="o">=</span><span class="n">scope</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">scheme</span><span class="sh">"</span><span class="p">),</span>
            <span class="n">query_args</span><span class="o">=</span><span class="n">scope</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">query_string</span><span class="sh">"</span><span class="p">,</span> <span class="sa">b</span><span class="sh">""</span><span class="p">),</span>
        <span class="p">)</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">__call__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">connection</span><span class="p">:</span> <span class="n">Connection</span><span class="p">):</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">rule</span><span class="p">,</span> <span class="n">args</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">get_url_binding_for_connection</span><span class="p">(</span><span class="n">connection</span><span class="p">).</span><span class="nf">match</span><span class="p">(</span>
                <span class="n">return_rule</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">method</span><span class="o">=</span><span class="n">connection</span><span class="p">.</span><span class="n">scope</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">method</span><span class="sh">"</span><span class="p">)</span>
            <span class="p">)</span>
        <span class="k">except</span> <span class="n">RequestRedirect</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="n">connection</span><span class="p">.</span><span class="n">resp_status_code</span> <span class="o">=</span> <span class="mi">302</span>
            <span class="n">connection</span><span class="p">.</span><span class="nf">put_resp_header</span><span class="p">(</span><span class="sh">"</span><span class="s">location</span><span class="sh">"</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">new_url</span><span class="p">)</span>
            <span class="k">return</span> <span class="k">await</span> <span class="n">connection</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">redirecting to: </span><span class="si">{</span><span class="n">e</span><span class="p">.</span><span class="n">new_url</span><span class="si">}</span><span class="sh">"</span><span class="p">,</span> <span class="n">finish</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
        <span class="k">except</span> <span class="n">MethodNotAllowed</span><span class="p">:</span>
            <span class="n">connection</span><span class="p">.</span><span class="n">resp_status_code</span> <span class="o">=</span> <span class="mi">405</span>
            <span class="k">return</span> <span class="k">await</span> <span class="n">connection</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="sa">b</span><span class="sh">""</span><span class="p">,</span> <span class="n">finish</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
        <span class="k">except</span> <span class="n">NotFound</span><span class="p">:</span>
            <span class="k">pass</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">handler</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">endpoint_to_handler</span><span class="p">[</span><span class="n">rule</span><span class="p">.</span><span class="n">endpoint</span><span class="p">]</span>
            <span class="n">res</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">handler</span><span class="p">(</span><span class="n">connection</span><span class="p">,</span> <span class="o">**</span><span class="n">args</span><span class="p">)</span>
            <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">res</span><span class="p">,</span> <span class="n">HttpResponse</span><span class="p">):</span>
                <span class="n">res</span><span class="p">.</span><span class="n">connection</span> <span class="o">=</span> <span class="n">connection</span>
                <span class="k">await</span> <span class="n">res</span>

</code></pre></div></div>
<p>In the <code class="language-plaintext highlighter-rouge">__call__</code> method, I’m checking if the returned type from the handler is <code class="language-plaintext highlighter-rouge">HttpResponsoe</code>, if it is indeed <code class="language-plaintext highlighter-rouge">HttpResponsoe</code>, the router can <code class="language-plaintext highlighter-rouge">await</code> the response and the response takes care of sending headers and body.</p>

<blockquote>
  <p><a href="https://werkzeug.palletsprojects.com/en/1.0.x/routing/">More details on</a> <a href="https://werkzeug.palletsprojects.com/en/1.0.x/routing/">werkzeug.routing</a></p>
</blockquote>

<p>Use this simple app to test it:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">aaf.connection</span> <span class="kn">import</span> <span class="n">Connection</span>
<span class="kn">from</span> <span class="n">aaf.response</span> <span class="kn">import</span> <span class="n">JsonResponse</span>
<span class="kn">from</span> <span class="n">aaf.routing</span> <span class="kn">import</span> <span class="n">Router</span>

<span class="n">router</span> <span class="o">=</span> <span class="nc">Router</span><span class="p">()</span>

<span class="nd">@router.route</span><span class="p">(</span><span class="sh">"</span><span class="s">/hello/&lt;name&gt;</span><span class="sh">"</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">hello</span><span class="p">(</span><span class="n">connection</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
    <span class="k">return</span> <span class="nc">JsonResponse</span><span class="p">({</span><span class="sh">'</span><span class="s">hello</span><span class="sh">'</span><span class="p">:</span> <span class="n">name</span><span class="p">})</span>


<span class="k">async</span> <span class="k">def</span> <span class="nf">app</span><span class="p">(</span><span class="n">scope</span><span class="p">,</span> <span class="n">receive</span><span class="p">,</span> <span class="n">send</span><span class="p">):</span>
    <span class="n">conn</span> <span class="o">=</span> <span class="nc">Connection</span><span class="p">(</span><span class="n">scope</span><span class="p">,</span> <span class="n">receive</span><span class="o">=</span><span class="n">receive</span><span class="p">,</span> <span class="n">send</span><span class="o">=</span><span class="n">send</span><span class="p">)</span>
    <span class="k">await</span> <span class="nf">router</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
</code></pre></div></div>
<p>Visiting <code class="language-plaintext highlighter-rouge">/hello/world</code> should return <code class="language-plaintext highlighter-rouge">{"hello": "world"}</code>.</p>

<p>The application gets longer, but hopefully, it’s clear that with more routes to handle a router makes it a lot easier to handle.</p>

<h2 id="router-to-asgi-application">Router to ASGI application</h2>
<p>Now, routers are great, but they are still not ASGI applications. Even though we can always write an ASGI application by hand, make a <code class="language-plaintext highlighter-rouge">Connection</code> out of it, and then call a router with the connection, it just doesn’t feel like an ASGI framework but more like an ASGI toolbox.</p>

<p>So, let’s write a helper function that turns a list of routers into an ASGI application. The idea of receiving a list of routers instead of just one is to allow an unhandled path to failover to the next router.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># __init__.py
</span><span class="kn">from</span> <span class="n">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Callable</span>
<span class="kn">from</span> <span class="n">.connection</span> <span class="kn">import</span> <span class="n">Connection</span>


<span class="k">def</span> <span class="nf">aaf</span><span class="p">(</span><span class="n">routers</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Callable</span><span class="p">]):</span>
    <span class="k">async</span> <span class="k">def</span> <span class="nf">asgi_app</span><span class="p">(</span><span class="n">scope</span><span class="p">,</span> <span class="n">receive</span><span class="p">,</span> <span class="n">send</span><span class="p">):</span>
        <span class="n">conn</span> <span class="o">=</span> <span class="nc">Connection</span><span class="p">(</span><span class="n">scope</span><span class="p">,</span> <span class="n">send</span><span class="o">=</span><span class="n">send</span><span class="p">,</span> <span class="n">receive</span><span class="o">=</span><span class="n">receive</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">router</span> <span class="ow">in</span> <span class="n">routers</span><span class="p">:</span>
            <span class="k">await</span> <span class="nf">router</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">conn</span><span class="p">.</span><span class="n">finished</span><span class="p">:</span>
                <span class="k">return</span>
        <span class="k">if</span> <span class="n">conn</span><span class="p">.</span><span class="n">started</span><span class="p">:</span>
            <span class="k">await</span> <span class="n">conn</span><span class="p">.</span><span class="nf">finish</span><span class="p">()</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">conn</span><span class="p">.</span><span class="n">resp_status_code</span> <span class="o">=</span> <span class="mi">404</span>
            <span class="k">await</span> <span class="n">conn</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="sh">"</span><span class="s">Not found</span><span class="sh">"</span><span class="p">,</span> <span class="n">finish</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">asgi_app</span>
</code></pre></div></div>
<p>It also includes a default 404 response.</p>

<p>With the help of this new function, the previous example can be rewritten as:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">aaf.connection</span> <span class="kn">import</span> <span class="n">Connection</span>
<span class="kn">from</span> <span class="n">aaf.response</span> <span class="kn">import</span> <span class="n">JsonResponse</span>
<span class="kn">from</span> <span class="n">aaf.routing</span> <span class="kn">import</span> <span class="n">Router</span>
<span class="kn">from</span> <span class="n">aaf</span> <span class="kn">import</span> <span class="n">aaf</span>

<span class="n">router</span> <span class="o">=</span> <span class="nc">Router</span><span class="p">()</span>

<span class="nd">@router.route</span><span class="p">(</span><span class="sh">"</span><span class="s">/hello/&lt;name&gt;</span><span class="sh">"</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">hello</span><span class="p">(</span><span class="n">connection</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
    <span class="k">return</span> <span class="nc">JsonResponse</span><span class="p">({</span><span class="sh">'</span><span class="s">hello</span><span class="sh">'</span><span class="p">:</span> <span class="n">name</span><span class="p">})</span>

<span class="n">app</span> <span class="o">=</span> <span class="nf">aaf</span><span class="p">([</span><span class="n">router</span><span class="p">])</span>
</code></pre></div></div>

<p>In fact the example defined in the <a href="#goal">goal</a> section should also work now. Give it a try!</p>

<h1 id="next-steps">Next steps</h1>

<p>An infinite number of things can be added to this framework. On the top of my head, there are</p>
<ol>
  <li>WebSocket support</li>
  <li>Lifecycle hooks</li>
  <li>More response types</li>
  <li>More <code class="language-plaintext highlighter-rouge">Connection</code> methods</li>
  <li>Built-in static file handling</li>
  <li>URL reversing</li>
  <li>Flask’s blueprint</li>
</ol>

<p>I will write some of the things in the near future. And I wish this post could give readers enough information on where to start implementing them on their own.</p>]]></content><author><name></name></author><category term="ASGI" /><summary type="html"><![CDATA[Learning about ASGI by building an ASGI web framework!]]></summary></entry><entry><title type="html">django-simple-task: An asynchronous Django 3 task runner</title><link href="/2019/12/30/django-simple-task.html" rel="alternate" type="text/html" title="django-simple-task: An asynchronous Django 3 task runner" /><published>2019-12-30T03:00:00-05:00</published><updated>2019-12-30T03:00:00-05:00</updated><id>/2019/12/30/django-simple-task</id><content type="html" xml:base="/2019/12/30/django-simple-task.html"><![CDATA[<p>django-simple-task runs background tasks in Django 3 without requiring other services and worker processes. It runs them in the same event loop as your ASGI application. It is not resilient as a proper task runner such as Celery, but works for some simple tasks and has less overall overheads.</p>

<p>You can run background tasks like this in a django view and it would just work.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">django_simple_task</span> <span class="kn">import</span> <span class="n">defer</span>

<span class="k">def</span> <span class="nf">task1</span><span class="p">():</span>
	<span class="n">time</span><span class="p">.</span><span class="nf">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
	<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">task1 done</span><span class="sh">"</span><span class="p">)</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">task2</span><span class="p">():</span>
	<span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="nf">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
	<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">task2 done</span><span class="sh">"</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">view</span><span class="p">(</span><span class="n">requests</span><span class="p">):</span>
	<span class="nf">defer</span><span class="p">(</span><span class="n">task1</span><span class="p">)</span>
	<span class="nf">defer</span><span class="p">(</span><span class="n">task2</span><span class="p">)</span>
	<span class="k">return</span> <span class="nc">HttpResponse</span><span class="p">(</span><span class="sa">b</span><span class="sh">"</span><span class="s">My View</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div>

<p>Here’s an overview of how it works:</p>

<ol>
  <li>On application start, a queue is created and a number of workers start to listen to the queue</li>
  <li>When defer is called, a task(function or coroutine function) is added to the queue</li>
  <li>When a worker gets a task, it runs it or delegates it to a threadpool</li>
  <li>On application shutdown, it waits for tasks to finish before exiting ASGI server</li>
</ol>

<hr />

<p><a href="https://pypi.org/project/django-simple-task/">View on PyPI</a></p>

<p><a href="https://github.com/ericls/django-simple-task">View on GitHub</a></p>

<p><a href="https://django-simple-task.readthedocs.io/">Read the docs</a></p>]]></content><author><name></name></author><category term="django," /><category term="ASGI," /><category term="python" /><summary type="html"><![CDATA[An asynchronous Django 3 task runner that does not require other services and separate worker processes]]></summary></entry><entry><title type="html">How to Watermark A Video with Python</title><link href="/2015/10/18/how-to-watermark-a-video-with-python.html" rel="alternate" type="text/html" title="How to Watermark A Video with Python" /><published>2015-10-18T00:00:00-04:00</published><updated>2015-10-18T00:00:00-04:00</updated><id>/2015/10/18/how-to-watermark-a-video-with-python</id><content type="html" xml:base="/2015/10/18/how-to-watermark-a-video-with-python.html"><![CDATA[<h2 id="why-watermarking">Why Watermarking?</h2>

<p>Preventing people from saving video content from your web site is never possible, because, firstly, video contents are always streamed to users’ device, users just cannot watch the video without retrieving it, secondly, people can always do a screen recording to save content.</p>

<p>Watermarking seems to be a reasonable solution to protect your content. If people download your video file and put it onto their websites, at least, people will know who made the video and/or your website.</p>

<h2 id="why-python">Why Python?</h2>

<p>There are lots of software packages that can do this, but they are not suitable for doing this to many videos in a server environment.  And, of course, life is short.</p>

<h2 id="the-library">The Library</h2>

<p><code class="language-plaintext highlighter-rouge">MoviePy </code> is the go-to choice for this type of task.</p>

<blockquote>
  <p>MoviePy is a Python module for video editing, which can be used for basic operations (like cuts, concatenations, title insertions), video compositing (a.k.a. non-linear editing), video processing, or to create advanced effects. It can read and write the most common video formats, including GIF.</p>
</blockquote>

<h2 id="the-steps">The Steps</h2>

<h3 id="first-installing-moviepy">First: Installing MoviePy</h3>

<p>Actually, <code class="language-plaintext highlighter-rouge">MoviePy </code> is not the only thing you need to install, it is dependent on many libraries and packages.</p>

<p>Most dependencies are installed automatically when you run <code class="language-plaintext highlighter-rouge">pip install moviepy</code>. There are two dependencies need to be installed manually, <code class="language-plaintext highlighter-rouge">ImageMagick</code> and <code class="language-plaintext highlighter-rouge">FFmpeg</code>.</p>

<p><code class="language-plaintext highlighter-rouge">ImageMagick</code> can be easily installed via your system’s package management tool.</p>

<p>According to the document of <code class="language-plaintext highlighter-rouge">MoviePy</code>,  <code class="language-plaintext highlighter-rouge">FFMPEG</code> will be automatically installed during the first use of <code class="language-plaintext highlighter-rouge">MoviePy</code>. However, the automatically installed <code class="language-plaintext highlighter-rouge">FFMPEG</code> was somehow unable to use some of the codecs depending on your system. It’s better to install <code class="language-plaintext highlighter-rouge">FFMPEG</code> by yourself.</p>

<p><code class="language-plaintext highlighter-rouge">FFmpeg</code> is not included in some of the system’s standard package repo, fortunately, <a href="https://www.ffmpeg.org/download.html">FFMPEG’s website</a> provides some useful information about how to get it running in different platforms.</p>

<h3 id="second-writing-the-script">Second, Writing the Script</h3>

<p>Writing the script is easy based on the example <a href="'http://zulko.github.io/moviepy/examples/ukulele_concerto.html'">HERE</a> in the documentation.</p>

<p>The example is actually more complicated that what we want, removing the unnecessary parts of the code:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">moviepy.editor</span> <span class="kn">import</span> <span class="o">*</span>

<span class="n">my_clip</span> <span class="o">=</span> <span class="nc">VideoFileClip</span><span class="p">(</span><span class="sh">"</span><span class="s">../../videos/moi_ukulele.MOV</span><span class="sh">"</span><span class="p">,</span> <span class="n">audio</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>  <span class="c1">#  The video file with audio enabled
</span>
<span class="n">w</span><span class="p">,</span><span class="n">h</span> <span class="o">=</span> <span class="n">my_clip</span><span class="p">.</span><span class="n">size</span>  <span class="c1"># size of the clip
</span>
<span class="c1"># A CLIP WITH A TEXT AND A BLACK SEMI-OPAQUE BACKGROUND
</span>
<span class="n">txt</span> <span class="o">=</span> <span class="nc">TextClip</span><span class="p">(</span><span class="sh">"</span><span class="s">THE WATERMARK TEXT</span><span class="sh">"</span><span class="p">,</span> <span class="n">font</span><span class="o">=</span><span class="sh">'</span><span class="s">Amiri-regular</span><span class="sh">'</span><span class="p">,</span>
	               <span class="n">color</span><span class="o">=</span><span class="sh">'</span><span class="s">white</span><span class="sh">'</span><span class="p">,</span><span class="n">fontsize</span><span class="o">=</span><span class="mi">24</span><span class="p">)</span>

<span class="n">txt_col</span> <span class="o">=</span> <span class="n">txt</span><span class="p">.</span><span class="nf">on_color</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="p">(</span><span class="n">my_clip</span><span class="p">.</span><span class="n">w</span> <span class="o">+</span> <span class="n">txt</span><span class="p">.</span><span class="n">w</span><span class="p">,</span><span class="n">txt</span><span class="p">.</span><span class="n">h</span><span class="o">-</span><span class="mi">10</span><span class="p">),</span>
                  <span class="n">color</span><span class="o">=</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span> <span class="n">pos</span><span class="o">=</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="sh">'</span><span class="s">center</span><span class="sh">'</span><span class="p">),</span> <span class="n">col_opacity</span><span class="o">=</span><span class="mf">0.6</span><span class="p">)</span>

<span class="c1"># This example demonstrates a moving text effect where the position is a function of time(t, in seconds).
# You can fix the position of the text manually, of course. Remember, you can use strings,
# like 'top', 'left' to specify the position
</span><span class="n">txt_mov</span> <span class="o">=</span> <span class="n">txt_col</span><span class="p">.</span><span class="nf">set_pos</span><span class="p">(</span> <span class="k">lambda</span> <span class="n">t</span><span class="p">:</span> <span class="p">(</span><span class="nf">max</span><span class="p">(</span><span class="n">w</span><span class="o">/</span><span class="mi">30</span><span class="p">,</span><span class="nf">int</span><span class="p">(</span><span class="n">w</span><span class="o">-</span><span class="mf">0.5</span><span class="o">*</span><span class="n">w</span><span class="o">*</span><span class="n">t</span><span class="p">)),</span>
                                  <span class="nf">max</span><span class="p">(</span><span class="mi">5</span><span class="o">*</span><span class="n">h</span><span class="o">/</span><span class="mi">6</span><span class="p">,</span><span class="nf">int</span><span class="p">(</span><span class="mi">100</span><span class="o">*</span><span class="n">t</span><span class="p">)))</span> <span class="p">)</span>

<span class="c1"># Write the file to disk
</span><span class="n">final</span> <span class="o">=</span> <span class="nc">CompositeVideoClip</span><span class="p">([</span><span class="n">my_clip</span><span class="p">,</span><span class="n">txt_mov</span><span class="p">])</span>
<span class="n">final</span><span class="p">.</span><span class="n">duration</span> <span class="o">=</span> <span class="n">my_clip</span><span class="p">.</span><span class="n">duration</span>
<span class="n">final</span><span class="p">.</span><span class="nf">write_videofile</span><span class="p">(</span><span class="sh">"</span><span class="s">OUT.mp4</span><span class="sh">"</span><span class="p">,</span><span class="n">fps</span><span class="o">=</span><span class="mi">24</span><span class="p">,</span><span class="n">codec</span><span class="o">=</span><span class="sh">'</span><span class="s">libx264</span><span class="sh">'</span><span class="p">)</span>
</code></pre></div></div>
<h3 id="third-batch-processing">Third: Batch Processing</h3>

<p>Rewriting the script, wrapping it into a function that takes input_file and output_file as arguments. Then, you can list the files to be watermarked with something like <code class="language-plaintext highlighter-rouge">glob.glob('./*.mp4')</code> and do an iteration over them.</p>

<h2 id="notes">Notes:</h2>

<ol>
  <li>To make the mp4 streamable, you’ll need to add <code class="language-plaintext highlighter-rouge">ffmpeg_params=['-movflags', 'faststart']</code> as an argument to <code class="language-plaintext highlighter-rouge">write_videofile</code> method.</li>
  <li>You can also add <code class="language-plaintext highlighter-rouge">threads</code> as an argument to accelerate the process.</li>
  <li>You may need to try different <code class="language-plaintext highlighter-rouge">codec</code> to make it work.</li>
</ol>]]></content><author><name></name></author><category term="Python," /><category term="MoviePy" /><summary type="html"><![CDATA[Why Watermarking?]]></summary></entry></feed>