703 lines
No EOL
60 KiB
HTML
703 lines
No EOL
60 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en"
|
||
xmlns:og="http://ogp.me/ns#"
|
||
xmlns:fb="https://www.facebook.com/2008/fbml">
|
||
<head>
|
||
<title>Let’s Build A Web Server. Part 2. - Ruslan's Blog</title>
|
||
<!-- Using the latest rendering mode for IE -->
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
||
|
||
|
||
<link rel="canonical" href="index.html">
|
||
|
||
<meta name="author" content="Ruslan Spivak" />
|
||
<meta name="description" content="Remember, in Part 1 I asked you a question: “How do you run a Django application, Flask application, and Pyramid application under your freshly minted Web server without making a single change to the server to accommodate all those different Web frameworks?” Read on to find out the answer. In …" />
|
||
|
||
<meta property="og:site_name" content="Ruslan's Blog" />
|
||
<meta property="og:type" content="article"/>
|
||
<meta property="og:title" content="Let’s Build A Web Server. Part 2."/>
|
||
<meta property="og:url" content="https://ruslanspivak.com/lsbaws-part2/"/>
|
||
<meta property="og:description" content="Remember, in Part 1 I asked you a question: “How do you run a Django application, Flask application, and Pyramid application under your freshly minted Web server without making a single change to the server to accommodate all those different Web frameworks?” Read on to find out the answer. In …"/>
|
||
<meta property="article:published_time" content="2015-04-06" />
|
||
<meta property="article:section" content="blog" />
|
||
<meta property="article:author" content="Ruslan Spivak" />
|
||
|
||
<meta name="twitter:card" content="summary">
|
||
<meta name="twitter:domain" content="https://ruslanspivak.com">
|
||
|
||
<!-- Bootstrap -->
|
||
<link rel="stylesheet" href="../theme/css/bootstrap.min.css" type="text/css"/>
|
||
<link href="../theme/css/font-awesome.min.css" rel="stylesheet">
|
||
|
||
<link href="../theme/css/pygments/tango.css" rel="stylesheet">
|
||
<link href="../theme/css/typogrify.css" rel="stylesheet">
|
||
<link rel="stylesheet" href="../theme/css/style.css" type="text/css"/>
|
||
<link href="../static/custom.css" rel="stylesheet">
|
||
|
||
<link href="../feeds/all.atom.xml" type="application/atom+xml" rel="alternate"
|
||
title="Ruslan's Blog ATOM Feed"/>
|
||
|
||
</head>
|
||
<body>
|
||
|
||
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
|
||
<div class="container">
|
||
<div class="navbar-header">
|
||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
|
||
<span class="sr-only">Toggle navigation</span>
|
||
<span class="icon-bar"></span>
|
||
<span class="icon-bar"></span>
|
||
<span class="icon-bar"></span>
|
||
</button>
|
||
<a href="../index.html" class="navbar-brand">
|
||
Ruslan's Blog </a>
|
||
</div>
|
||
<div class="collapse navbar-collapse navbar-ex1-collapse">
|
||
<ul class="nav navbar-nav">
|
||
</ul>
|
||
<ul class="nav navbar-nav navbar-right">
|
||
<li><a href="../pages/about.html"><i class="fa fa-question"></i><span class="icon-label">About</span></a></li>
|
||
<li><a href="../archives.html"><i class="fa fa-th-list"></i><span class="icon-label">Archives</span></a></li>
|
||
</ul>
|
||
</div>
|
||
<!-- /.navbar-collapse -->
|
||
</div>
|
||
</div> <!-- /.navbar -->
|
||
<!-- Banner -->
|
||
<!-- End Banner -->
|
||
<div class="container">
|
||
<div class="row">
|
||
<div class="col-sm-9">
|
||
|
||
<section id="content">
|
||
<article>
|
||
<header class="page-header">
|
||
<h1>
|
||
<a href="index.html"
|
||
rel="bookmark"
|
||
title="Permalink to Let’s Build A Web Server. Part 2.">
|
||
Let’s Build A Web Server. Part 2.
|
||
</a>
|
||
</h1>
|
||
</header>
|
||
<div class="entry-content">
|
||
<div class="panel">
|
||
<div class="panel-body">
|
||
<footer class="post-info">
|
||
<span class="label label-default">Date</span>
|
||
<span class="published">
|
||
<i class="fa fa-calendar"></i><time datetime="2015-04-06T07:00:00-04:00"> Mon, April 06, 2015</time>
|
||
</span>
|
||
|
||
|
||
|
||
|
||
</footer><!-- /.post-info --> </div>
|
||
</div>
|
||
<p>Remember, in <a href="../lsbaws-part1/index.html" title="Part 1">Part 1</a> I asked you a question: “How do you run a Django application, Flask application, and Pyramid application under your freshly minted Web server without making a single change to the server to accommodate all those different Web frameworks?” Read on to find out the answer.</p>
|
||
<p>In the past, your choice of a Python Web framework would limit your choice of usable Web servers, and vice versa. If the framework and the server were designed to work together, then you were okay:</p>
|
||
<p><img alt="Server Framework Fit" src="lsbaws_part2_before_wsgi.png" width="640"></p>
|
||
<p>But you could have been faced (and maybe you were) with the following problem when trying to combine a server and a framework that weren’t designed to work together:</p>
|
||
<p><img alt="Server Framework Clash" src="lsbaws_part2_after_wsgi.png" width="640"></p>
|
||
<p>Basically you had to use what worked together and not what you might have wanted to use.</p>
|
||
<p>So, how do you then make sure that you can run your Web server with multiple Web frameworks without making code changes either to the Web server or to the Web frameworks?
|
||
And the answer to that problem became the <strong>Python Web Server Gateway Interface</strong> (or <a href="https://www.python.org/dev/peps/pep-0333/" title="WSGI"><span class="caps">WSGI</span></a> for short, pronounced <em>“wizgy”</em>).</p>
|
||
<p><img alt="WSGI Interface" src="lsbaws_part2_wsgi_idea.png" width="480"></p>
|
||
<p><a href="https://www.python.org/dev/peps/pep-0333/" title="WSGI"><span class="caps">WSGI</span></a> allowed developers to separate choice of a Web framework from choice of a Web server. Now you can actually mix and match Web servers and Web frameworks and choose a pairing that suits your needs. You can run <a href="https://www.djangoproject.com/" title="Django">Django</a>, <a href="http://flask.pocoo.org/" title="Flask">Flask</a>, or <a href="http://trypyramid.com/" title="Pyramid">Pyramid</a>, for example, with <a href="http://gunicorn.org/" title="Gunicorn">Gunicorn</a> or <a href="http://uwsgi-docs.readthedocs.org" title="uWSGI">Nginx/uWSGI</a> or <a href="http://waitress.readthedocs.org" title="Waitress">Waitress</a>. Real mix and match, thanks to the <span class="caps">WSGI</span> support in both servers and frameworks:</p>
|
||
<p><img alt="Mix & Match" src="lsbaws_part2_wsgi_interop.png" width="640"></p>
|
||
<p>So, <a href="https://www.python.org/dev/peps/pep-0333/" title="WSGI"><span class="caps">WSGI</span></a> is the answer to the question I asked you in <a href="../lsbaws-part1/index.html" title="Part 1">Part 1</a> and repeated at the beginning of this article. Your Web server must implement the server portion of a <span class="caps">WSGI</span> interface and all modern Python Web Frameworks already implement the framework side of the <span class="caps">WSGI</span> interface, which allows you to use them with your Web server without ever modifying your server’s code to accommodate a particular Web framework.</p>
|
||
<p>Now you know that <span class="caps">WSGI</span> support by Web servers and Web frameworks allows you to choose a pairing that suits you, but it is also beneficial to server and framework developers because they can focus on their preferred area of specialization and not step on each other’s toes. Other languages have similar interfaces too: Java, for example, has <a href="http://en.wikipedia.org/wiki/Java_servlet" title="Servlet API">Servlet <span class="caps">API</span></a> and Ruby has <a href="http://en.wikipedia.org/wiki/Rack_%28web_server_interface%29" title="Rack">Rack</a>.</p>
|
||
<p>It’s all good, but I bet you are saying: “Show me the code!”
|
||
Okay, take a look at this pretty minimalistic <span class="caps">WSGI</span> server implementation:</p>
|
||
<div class="highlight"><pre><span></span><span class="c1"># Tested with Python 3.7+ (Mac OS X)</span>
|
||
<span class="kn">import</span> <span class="nn">io</span>
|
||
<span class="kn">import</span> <span class="nn">socket</span>
|
||
<span class="kn">import</span> <span class="nn">sys</span>
|
||
|
||
|
||
<span class="k">class</span> <span class="nc">WSGIServer</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
|
||
|
||
<span class="n">address_family</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span>
|
||
<span class="n">socket_type</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span>
|
||
<span class="n">request_queue_size</span> <span class="o">=</span> <span class="mi">1</span>
|
||
|
||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">server_address</span><span class="p">):</span>
|
||
<span class="c1"># Create a listening socket</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">listen_socket</span> <span class="o">=</span> <span class="n">listen_socket</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">address_family</span><span class="p">,</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">socket_type</span>
|
||
<span class="p">)</span>
|
||
<span class="c1"># Allow to reuse the same address</span>
|
||
<span class="n">listen_socket</span><span class="o">.</span><span class="n">setsockopt</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">SOL_SOCKET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SO_REUSEADDR</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
|
||
<span class="c1"># Bind</span>
|
||
<span class="n">listen_socket</span><span class="o">.</span><span class="n">bind</span><span class="p">(</span><span class="n">server_address</span><span class="p">)</span>
|
||
<span class="c1"># Activate</span>
|
||
<span class="n">listen_socket</span><span class="o">.</span><span class="n">listen</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">request_queue_size</span><span class="p">)</span>
|
||
<span class="c1"># Get server host name and port</span>
|
||
<span class="n">host</span><span class="p">,</span> <span class="n">port</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">listen_socket</span><span class="o">.</span><span class="n">getsockname</span><span class="p">()[:</span><span class="mi">2</span><span class="p">]</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">server_name</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">getfqdn</span><span class="p">(</span><span class="n">host</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">server_port</span> <span class="o">=</span> <span class="n">port</span>
|
||
<span class="c1"># Return headers set by Web framework/Web application</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">headers_set</span> <span class="o">=</span> <span class="p">[]</span>
|
||
|
||
<span class="k">def</span> <span class="nf">set_app</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">application</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">application</span> <span class="o">=</span> <span class="n">application</span>
|
||
|
||
<span class="k">def</span> <span class="nf">serve_forever</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="n">listen_socket</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">listen_socket</span>
|
||
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
|
||
<span class="c1"># New client connection</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">client_connection</span><span class="p">,</span> <span class="n">client_address</span> <span class="o">=</span> <span class="n">listen_socket</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
|
||
<span class="c1"># Handle one request and close the client connection. Then</span>
|
||
<span class="c1"># loop over to wait for another client connection</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">handle_one_request</span><span class="p">()</span>
|
||
|
||
<span class="k">def</span> <span class="nf">handle_one_request</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="n">request_data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">client_connection</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">request_data</span> <span class="o">=</span> <span class="n">request_data</span> <span class="o">=</span> <span class="n">request_data</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'utf-8'</span><span class="p">)</span>
|
||
<span class="c1"># Print formatted request data a la 'curl -v'</span>
|
||
<span class="k">print</span><span class="p">(</span><span class="s1">''</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
|
||
<span class="n">f</span><span class="s1">'< {line}</span><span class="se">\n</span><span class="s1">'</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">request_data</span><span class="o">.</span><span class="n">splitlines</span><span class="p">()</span>
|
||
<span class="p">))</span>
|
||
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">parse_request</span><span class="p">(</span><span class="n">request_data</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Construct environment dictionary using request data</span>
|
||
<span class="n">env</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_environ</span><span class="p">()</span>
|
||
|
||
<span class="c1"># It's time to call our application callable and get</span>
|
||
<span class="c1"># back a result that will become HTTP response body</span>
|
||
<span class="n">result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">application</span><span class="p">(</span><span class="n">env</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">start_response</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Construct a response and send it back to the client</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">finish_response</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">parse_request</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">text</span><span class="p">):</span>
|
||
<span class="n">request_line</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">splitlines</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span>
|
||
<span class="n">request_line</span> <span class="o">=</span> <span class="n">request_line</span><span class="o">.</span><span class="n">rstrip</span><span class="p">(</span><span class="s1">'</span><span class="se">\r\n</span><span class="s1">'</span><span class="p">)</span>
|
||
<span class="c1"># Break down the request line into components</span>
|
||
<span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">request_method</span><span class="p">,</span> <span class="c1"># GET</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">path</span><span class="p">,</span> <span class="c1"># /hello</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">request_version</span> <span class="c1"># HTTP/1.1</span>
|
||
<span class="p">)</span> <span class="o">=</span> <span class="n">request_line</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
|
||
|
||
<span class="k">def</span> <span class="nf">get_environ</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="n">env</span> <span class="o">=</span> <span class="p">{}</span>
|
||
<span class="c1"># The following code snippet does not follow PEP8 conventions</span>
|
||
<span class="c1"># but it's formatted the way it is for demonstration purposes</span>
|
||
<span class="c1"># to emphasize the required variables and their values</span>
|
||
<span class="c1">#</span>
|
||
<span class="c1"># Required WSGI variables</span>
|
||
<span class="n">env</span><span class="p">[</span><span class="s1">'wsgi.version'</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
|
||
<span class="n">env</span><span class="p">[</span><span class="s1">'wsgi.url_scheme'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'http'</span>
|
||
<span class="n">env</span><span class="p">[</span><span class="s1">'wsgi.input'</span><span class="p">]</span> <span class="o">=</span> <span class="n">io</span><span class="o">.</span><span class="n">StringIO</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">request_data</span><span class="p">)</span>
|
||
<span class="n">env</span><span class="p">[</span><span class="s1">'wsgi.errors'</span><span class="p">]</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">stderr</span>
|
||
<span class="n">env</span><span class="p">[</span><span class="s1">'wsgi.multithread'</span><span class="p">]</span> <span class="o">=</span> <span class="bp">False</span>
|
||
<span class="n">env</span><span class="p">[</span><span class="s1">'wsgi.multiprocess'</span><span class="p">]</span> <span class="o">=</span> <span class="bp">False</span>
|
||
<span class="n">env</span><span class="p">[</span><span class="s1">'wsgi.run_once'</span><span class="p">]</span> <span class="o">=</span> <span class="bp">False</span>
|
||
<span class="c1"># Required CGI variables</span>
|
||
<span class="n">env</span><span class="p">[</span><span class="s1">'REQUEST_METHOD'</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">request_method</span> <span class="c1"># GET</span>
|
||
<span class="n">env</span><span class="p">[</span><span class="s1">'PATH_INFO'</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">path</span> <span class="c1"># /hello</span>
|
||
<span class="n">env</span><span class="p">[</span><span class="s1">'SERVER_NAME'</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">server_name</span> <span class="c1"># localhost</span>
|
||
<span class="n">env</span><span class="p">[</span><span class="s1">'SERVER_PORT'</span><span class="p">]</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">server_port</span><span class="p">)</span> <span class="c1"># 8888</span>
|
||
<span class="k">return</span> <span class="n">env</span>
|
||
|
||
<span class="k">def</span> <span class="nf">start_response</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">status</span><span class="p">,</span> <span class="n">response_headers</span><span class="p">,</span> <span class="n">exc_info</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
|
||
<span class="c1"># Add necessary server headers</span>
|
||
<span class="n">server_headers</span> <span class="o">=</span> <span class="p">[</span>
|
||
<span class="p">(</span><span class="s1">'Date'</span><span class="p">,</span> <span class="s1">'Mon, 15 Jul 2019 5:54:48 GMT'</span><span class="p">),</span>
|
||
<span class="p">(</span><span class="s1">'Server'</span><span class="p">,</span> <span class="s1">'WSGIServer 0.2'</span><span class="p">),</span>
|
||
<span class="p">]</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">headers_set</span> <span class="o">=</span> <span class="p">[</span><span class="n">status</span><span class="p">,</span> <span class="n">response_headers</span> <span class="o">+</span> <span class="n">server_headers</span><span class="p">]</span>
|
||
<span class="c1"># To adhere to WSGI specification the start_response must return</span>
|
||
<span class="c1"># a 'write' callable. We simplicity's sake we'll ignore that detail</span>
|
||
<span class="c1"># for now.</span>
|
||
<span class="c1"># return self.finish_response</span>
|
||
|
||
<span class="k">def</span> <span class="nf">finish_response</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">result</span><span class="p">):</span>
|
||
<span class="k">try</span><span class="p">:</span>
|
||
<span class="n">status</span><span class="p">,</span> <span class="n">response_headers</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">headers_set</span>
|
||
<span class="n">response</span> <span class="o">=</span> <span class="n">f</span><span class="s1">'HTTP/1.1 {status}</span><span class="se">\r\n</span><span class="s1">'</span>
|
||
<span class="k">for</span> <span class="n">header</span> <span class="ow">in</span> <span class="n">response_headers</span><span class="p">:</span>
|
||
<span class="n">response</span> <span class="o">+=</span> <span class="s1">'{0}: {1}</span><span class="se">\r\n</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="o">*</span><span class="n">header</span><span class="p">)</span>
|
||
<span class="n">response</span> <span class="o">+=</span> <span class="s1">'</span><span class="se">\r\n</span><span class="s1">'</span>
|
||
<span class="k">for</span> <span class="n">data</span> <span class="ow">in</span> <span class="n">result</span><span class="p">:</span>
|
||
<span class="n">response</span> <span class="o">+=</span> <span class="n">data</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'utf-8'</span><span class="p">)</span>
|
||
<span class="c1"># Print formatted response data a la 'curl -v'</span>
|
||
<span class="k">print</span><span class="p">(</span><span class="s1">''</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
|
||
<span class="n">f</span><span class="s1">'> {line}</span><span class="se">\n</span><span class="s1">'</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">splitlines</span><span class="p">()</span>
|
||
<span class="p">))</span>
|
||
<span class="n">response_bytes</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">encode</span><span class="p">()</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">client_connection</span><span class="o">.</span><span class="n">sendall</span><span class="p">(</span><span class="n">response_bytes</span><span class="p">)</span>
|
||
<span class="k">finally</span><span class="p">:</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">client_connection</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||
|
||
|
||
<span class="n">SERVER_ADDRESS</span> <span class="o">=</span> <span class="p">(</span><span class="n">HOST</span><span class="p">,</span> <span class="n">PORT</span><span class="p">)</span> <span class="o">=</span> <span class="s1">''</span><span class="p">,</span> <span class="mi">8888</span>
|
||
|
||
|
||
<span class="k">def</span> <span class="nf">make_server</span><span class="p">(</span><span class="n">server_address</span><span class="p">,</span> <span class="n">application</span><span class="p">):</span>
|
||
<span class="n">server</span> <span class="o">=</span> <span class="n">WSGIServer</span><span class="p">(</span><span class="n">server_address</span><span class="p">)</span>
|
||
<span class="n">server</span><span class="o">.</span><span class="n">set_app</span><span class="p">(</span><span class="n">application</span><span class="p">)</span>
|
||
<span class="k">return</span> <span class="n">server</span>
|
||
|
||
|
||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
|
||
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span> <span class="o"><</span> <span class="mi">2</span><span class="p">:</span>
|
||
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="s1">'Provide a WSGI application object as module:callable'</span><span class="p">)</span>
|
||
<span class="n">app_path</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
|
||
<span class="n">module</span><span class="p">,</span> <span class="n">application</span> <span class="o">=</span> <span class="n">app_path</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">':'</span><span class="p">)</span>
|
||
<span class="n">module</span> <span class="o">=</span> <span class="nb">__import__</span><span class="p">(</span><span class="n">module</span><span class="p">)</span>
|
||
<span class="n">application</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">module</span><span class="p">,</span> <span class="n">application</span><span class="p">)</span>
|
||
<span class="n">httpd</span> <span class="o">=</span> <span class="n">make_server</span><span class="p">(</span><span class="n">SERVER_ADDRESS</span><span class="p">,</span> <span class="n">application</span><span class="p">)</span>
|
||
<span class="k">print</span><span class="p">(</span><span class="n">f</span><span class="s1">'WSGIServer: Serving HTTP on port {PORT} ...</span><span class="se">\n</span><span class="s1">'</span><span class="p">)</span>
|
||
<span class="n">httpd</span><span class="o">.</span><span class="n">serve_forever</span><span class="p">()</span>
|
||
</pre></div>
|
||
|
||
|
||
<p>It’s definitely bigger than the server code in <a href="../lsbaws-part1/index.html" title="Part 1">Part 1</a>, but it’s also small enough (just under 150 lines) for you to understand without getting bogged down in details. The above server also does more - it can run your basic Web application written with your beloved Web framework, be it Pyramid, Flask, Django, or some other Python <span class="caps">WSGI</span> framework.</p>
|
||
<p>Don’t believe me? Try it and see for yourself. Save the above code as <em>webserver2.py</em> or download it directly from <a href="https://github.com/rspivak/lsbaws/blob/master/part2/webserver2.py">GitHub</a>. If you try to run it without any parameters it’s going to complain and exit.</p>
|
||
<div class="highlight"><pre><span></span>$ python webserver2.py
|
||
Provide a WSGI application object as module:callable
|
||
</pre></div>
|
||
|
||
|
||
<p>It really wants to serve your Web application and that’s where the fun begins. To run the server the only thing you need installed is Python (Python 3.7+, to be exact). But to run applications written with Pyramid, Flask, and Django you need to install those frameworks first. Let’s install all three of them. My preferred method is by using <a href="https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments" target="_blank">venv</a> (it is available by default in Python 3.3 and later). Just follow the steps below to create and activate a virtual environment and then install all three Web frameworks.</p>
|
||
<div class="highlight"><pre><span></span>$ python3 -m venv lsbaws
|
||
$ ls lsbaws
|
||
bin include lib pyvenv.cfg
|
||
$ <span class="nb">source</span> lsbaws/bin/activate
|
||
<span class="o">(</span>lsbaws<span class="o">)</span> $ pip install -U pip
|
||
<span class="o">(</span>lsbaws<span class="o">)</span> $ pip install pyramid
|
||
<span class="o">(</span>lsbaws<span class="o">)</span> $ pip install flask
|
||
<span class="o">(</span>lsbaws<span class="o">)</span> $ pip install django
|
||
</pre></div>
|
||
|
||
|
||
<p>At this point you need to create a Web application.
|
||
Let’s start with <a href="http://trypyramid.com/" title="Pyramid">Pyramid</a> first.
|
||
Save the following code as <em>pyramidapp.py</em> to the same directory where you saved <em>webserver2.py</em> or download the file directly from <a href="https://github.com/rspivak/lsbaws/blob/master/part2/pyramidapp.py">GitHub</a>:</p>
|
||
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">pyramid.config</span> <span class="kn">import</span> <span class="n">Configurator</span>
|
||
<span class="kn">from</span> <span class="nn">pyramid.response</span> <span class="kn">import</span> <span class="n">Response</span>
|
||
|
||
|
||
<span class="k">def</span> <span class="nf">hello_world</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span>
|
||
<span class="s1">'Hello world from Pyramid!</span><span class="se">\n</span><span class="s1">'</span><span class="p">,</span>
|
||
<span class="n">content_type</span><span class="o">=</span><span class="s1">'text/plain'</span><span class="p">,</span>
|
||
<span class="p">)</span>
|
||
|
||
<span class="n">config</span> <span class="o">=</span> <span class="n">Configurator</span><span class="p">()</span>
|
||
<span class="n">config</span><span class="o">.</span><span class="n">add_route</span><span class="p">(</span><span class="s1">'hello'</span><span class="p">,</span> <span class="s1">'/hello'</span><span class="p">)</span>
|
||
<span class="n">config</span><span class="o">.</span><span class="n">add_view</span><span class="p">(</span><span class="n">hello_world</span><span class="p">,</span> <span class="n">route_name</span><span class="o">=</span><span class="s1">'hello'</span><span class="p">)</span>
|
||
<span class="n">app</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">make_wsgi_app</span><span class="p">()</span>
|
||
</pre></div>
|
||
|
||
|
||
<p>Now you’re ready to serve your Pyramid application with your very own Web server:</p>
|
||
<div class="highlight"><pre><span></span><span class="o">(</span>lsbaws<span class="o">)</span> $ python webserver2.py pyramidapp:app
|
||
WSGIServer: Serving HTTP on port <span class="m">8888</span> ...
|
||
</pre></div>
|
||
|
||
|
||
<p>You just told your server to load the <em>‘app’</em> callable from the python module <em>‘pyramidapp’</em> Your server is now ready to take requests and forward them to your Pyramid application. The application only handles one route now: the <em>/hello</em> route.
|
||
Type <a href="http://localhost:8888/hello">http://localhost:8888/hello</a> address into your browser, press Enter, and observe the result:</p>
|
||
<p><img alt="Pyramid" src="lsbaws_part2_browser_pyramid.png"></p>
|
||
<p>You can also test the server on the command line using the <em>‘curl’</em> utility:</p>
|
||
<div class="highlight"><pre><span></span>$ curl -v http://localhost:8888/hello
|
||
...
|
||
</pre></div>
|
||
|
||
|
||
<p>Check what the server and <em>curl</em> prints to standard output.</p>
|
||
<p>Now onto <a href="http://flask.pocoo.org/" title="Flask">Flask</a>. Let’s follow the same steps.</p>
|
||
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span>
|
||
<span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Response</span>
|
||
<span class="n">flask_app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="s1">'flaskapp'</span><span class="p">)</span>
|
||
|
||
|
||
<span class="nd">@flask_app.route</span><span class="p">(</span><span class="s1">'/hello'</span><span class="p">)</span>
|
||
<span class="k">def</span> <span class="nf">hello_world</span><span class="p">():</span>
|
||
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span>
|
||
<span class="s1">'Hello world from Flask!</span><span class="se">\n</span><span class="s1">'</span><span class="p">,</span>
|
||
<span class="n">mimetype</span><span class="o">=</span><span class="s1">'text/plain'</span>
|
||
<span class="p">)</span>
|
||
|
||
<span class="n">app</span> <span class="o">=</span> <span class="n">flask_app</span><span class="o">.</span><span class="n">wsgi_app</span>
|
||
</pre></div>
|
||
|
||
|
||
<p>Save the above code as <em>flaskapp.py</em> or download it from <a href="https://github.com/rspivak/lsbaws/blob/master/part2/flaskapp.py">GitHub</a> and run the server as:</p>
|
||
<div class="highlight"><pre><span></span><span class="o">(</span>lsbaws<span class="o">)</span> $ python webserver2.py flaskapp:app
|
||
WSGIServer: Serving HTTP on port <span class="m">8888</span> ...
|
||
</pre></div>
|
||
|
||
|
||
<p>Now type in the <a href="http://localhost:8888/hello">http://localhost:8888/hello</a> into your browser and press Enter:</p>
|
||
<p><img alt="Flask" src="lsbaws_part2_browser_flask.png"></p>
|
||
<p>Again, try <em>‘curl’</em> and see for yourself that the server returns a message generated by the Flask application:</p>
|
||
<div class="highlight"><pre><span></span>$ curl -v http://localhost:8888/hello
|
||
...
|
||
</pre></div>
|
||
|
||
|
||
<p>Can the server also handle a <a href="https://www.djangoproject.com/" title="Django">Django</a> application? Try it out!
|
||
It’s a little bit more involved, though, and I would recommend cloning the whole repo and use <a href="https://github.com/rspivak/lsbaws/blob/master/part2/djangoapp.py">djangoapp.py</a>, which is part of the <a href="https://github.com/rspivak/lsbaws/">GitHub repository</a>.
|
||
Here is the source code which basically adds the Django <em>‘helloworld’</em> project (pre-created using Django’s <em>django-admin.py startproject</em> command) to the current Python path and then imports the project’s <span class="caps">WSGI</span> application.</p>
|
||
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">sys</span>
|
||
<span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="s1">'./helloworld'</span><span class="p">)</span>
|
||
<span class="kn">from</span> <span class="nn">helloworld</span> <span class="kn">import</span> <span class="n">wsgi</span>
|
||
|
||
|
||
<span class="n">app</span> <span class="o">=</span> <span class="n">wsgi</span><span class="o">.</span><span class="n">application</span>
|
||
</pre></div>
|
||
|
||
|
||
<p>Save the above code as <em>djangoapp.py</em> and run the Django application with your Web server:</p>
|
||
<div class="highlight"><pre><span></span><span class="o">(</span>lsbaws<span class="o">)</span> $ python webserver2.py djangoapp:app
|
||
WSGIServer: Serving HTTP on port <span class="m">8888</span> ...
|
||
</pre></div>
|
||
|
||
|
||
<p>Type in the following address and press Enter:</p>
|
||
<p><img alt="Django" src="lsbaws_part2_browser_django.png"></p>
|
||
<p>And as you’ve already done a couple of times before, you can test it on the command line, too, and confirm that it’s the Django application that handles your requests this time around:</p>
|
||
<div class="highlight"><pre><span></span>$ curl -v http://localhost:8888/hello
|
||
...
|
||
</pre></div>
|
||
|
||
|
||
<p>Did you try it? Did you make sure the server works with those three frameworks? If not, then please do so. Reading is important, but this series is about rebuilding and that means you need to get your hands dirty. Go and try it. I will wait for you, don’t worry. No seriously, you must try it and, better yet, retype everything yourself and make sure that it works as expected.</p>
|
||
<p>Okay, you’ve experienced the power of <span class="caps">WSGI</span>: it allows you to mix and match your Web servers and Web frameworks. <span class="caps">WSGI</span> provides a minimal interface between Python Web servers and Python Web Frameworks. It’s very simple and it’s easy to implement on both the server and the framework side. The following code snippet shows the server and the framework side of the interface:</p>
|
||
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">run_application</span><span class="p">(</span><span class="n">application</span><span class="p">):</span>
|
||
<span class="sd">"""Server code."""</span>
|
||
<span class="c1"># This is where an application/framework stores</span>
|
||
<span class="c1"># an HTTP status and HTTP response headers for the server</span>
|
||
<span class="c1"># to transmit to the client</span>
|
||
<span class="n">headers_set</span> <span class="o">=</span> <span class="p">[]</span>
|
||
<span class="c1"># Environment dictionary with WSGI/CGI variables</span>
|
||
<span class="n">environ</span> <span class="o">=</span> <span class="p">{}</span>
|
||
|
||
<span class="k">def</span> <span class="nf">start_response</span><span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="n">response_headers</span><span class="p">,</span> <span class="n">exc_info</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
|
||
<span class="n">headers_set</span><span class="p">[:]</span> <span class="o">=</span> <span class="p">[</span><span class="n">status</span><span class="p">,</span> <span class="n">response_headers</span><span class="p">]</span>
|
||
|
||
<span class="c1"># Server invokes the ‘application' callable and gets back the</span>
|
||
<span class="c1"># response body</span>
|
||
<span class="n">result</span> <span class="o">=</span> <span class="n">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">)</span>
|
||
<span class="c1"># Server builds an HTTP response and transmits it to the client</span>
|
||
<span class="o">...</span>
|
||
|
||
<span class="k">def</span> <span class="nf">app</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
|
||
<span class="sd">"""A barebones WSGI app."""</span>
|
||
<span class="n">start_response</span><span class="p">(</span><span class="s1">'200 OK'</span><span class="p">,</span> <span class="p">[(</span><span class="s1">'Content-Type'</span><span class="p">,</span> <span class="s1">'text/plain'</span><span class="p">)])</span>
|
||
<span class="k">return</span> <span class="p">[</span><span class="sa">b</span><span class="s1">'Hello world!'</span><span class="p">]</span>
|
||
|
||
<span class="n">run_application</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>
|
||
</pre></div>
|
||
|
||
|
||
<p>Here is how it works:</p>
|
||
<ol>
|
||
<li>The framework provides an <em>‘application’</em> callable (The <span class="caps">WSGI</span> specification doesn’t prescribe how that should be implemented)</li>
|
||
<li>The server invokes the <em>‘application’</em> callable for each request it receives from an <span class="caps">HTTP</span> client. It passes a dictionary <em>‘environ’</em> containing <span class="caps">WSGI</span>/<span class="caps">CGI</span> variables and a <em>‘start_response’</em> callable as arguments to the <em>‘application’</em> callable.</li>
|
||
<li>The framework/application generates an <span class="caps">HTTP</span> status and <span class="caps">HTTP</span> response headers and passes them to the <em>‘start_response’</em> callable for the server to store them. The framework/application also returns a response body.</li>
|
||
<li>The server combines the status, the response headers, and the response body into an <span class="caps">HTTP</span> response and transmits it to the client (This step is not part of the specification but it’s the next logical step in the flow and I added it for clarity)</li>
|
||
</ol>
|
||
<p>And here is a visual representation of the interface:</p>
|
||
<p><img alt="WSGI Interface" src="lsbaws_part2_wsgi_interface.png"></p>
|
||
<p>So far, you’ve seen the Pyramid, Flask, and Django Web applications and you’ve seen the server code that implements the server side of the <span class="caps">WSGI</span> specification. You’ve even seen the barebones <span class="caps">WSGI</span> application code snippet that doesn’t use any framework.</p>
|
||
<p>The thing is that when you write a Web application using one of those frameworks you work at a higher level and don’t work with <span class="caps">WSGI</span> directly, but I know you’re curious about the framework side of the <span class="caps">WSGI</span> interface, too because you’re reading this article. So, let’s create a minimalistic <span class="caps">WSGI</span> Web application/Web framework without using Pyramid, Flask, or Django and run it with your server:</p>
|
||
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">app</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
|
||
<span class="sd">"""A barebones WSGI application.</span>
|
||
|
||
<span class="sd"> This is a starting point for your own Web framework :)</span>
|
||
<span class="sd"> """</span>
|
||
<span class="n">status</span> <span class="o">=</span> <span class="s1">'200 OK'</span>
|
||
<span class="n">response_headers</span> <span class="o">=</span> <span class="p">[(</span><span class="s1">'Content-Type'</span><span class="p">,</span> <span class="s1">'text/plain'</span><span class="p">)]</span>
|
||
<span class="n">start_response</span><span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="n">response_headers</span><span class="p">)</span>
|
||
<span class="k">return</span> <span class="p">[</span><span class="sa">b</span><span class="s1">'Hello world from a simple WSGI application!</span><span class="se">\n</span><span class="s1">'</span><span class="p">]</span>
|
||
</pre></div>
|
||
|
||
|
||
<p>Again, save the above code in <em>wsgiapp.py</em> file or download it from <a href="https://github.com/rspivak/lsbaws/blob/master/part2/wsgiapp.py">GitHub</a> directly and run the application under your Web server as:</p>
|
||
<div class="highlight"><pre><span></span><span class="o">(</span>lsbaws<span class="o">)</span> $ python webserver2.py wsgiapp:app
|
||
WSGIServer: Serving HTTP on port <span class="m">8888</span> ...
|
||
</pre></div>
|
||
|
||
|
||
<p>Type in the following address and press Enter. This is the result you should see:</p>
|
||
<p><img alt="Simple WSGI Application" src="lsbaws_part2_browser_simple_wsgi_app.png"></p>
|
||
<p>You just wrote your very own minimalistic <span class="caps">WSGI</span> Web framework while learning about how to create a Web server! Outrageous.</p>
|
||
<p>Now, let’s get back to what the server transmits to the client. Here is the <span class="caps">HTTP</span> response the server generates when you call your Pyramid application using an <span class="caps">HTTP</span> client:</p>
|
||
<p><img alt="HTTP Response Part 1" src="lsbaws_part2_http_response.png" width="640"></p>
|
||
<p>The response has some familiar parts that you saw in <a href="../lsbaws-part1/index.html" title="Part 1">Part 1</a> but it also has something new. It has, for example, four <a href="http://en.wikipedia.org/wiki/List_of_HTTP_header_fields" title="HTTP header fields"><span class="caps">HTTP</span> headers</a> that you haven’t seen before: <em>Content-Type</em>, <em>Content-Length</em>, <em>Date</em>, and <em>Server</em>. Those are the headers that a response from a Web server generally should have. None of them are strictly required, though. The purpose of the headers is to transmit additional information about the <span class="caps">HTTP</span> request/response.</p>
|
||
<p>Now that you know more about the <span class="caps">WSGI</span> interface, here is the same <span class="caps">HTTP</span> response with some more information about what parts produced it:</p>
|
||
<p><img alt="HTTP Response Part 2" src="lsbaws_part2_http_response_explanation.png"></p>
|
||
<p>I haven’t said anything about the <strong>‘environ’</strong> dictionary yet, but basically it’s a Python dictionary that must contain certain <span class="caps">WSGI</span> and <span class="caps">CGI</span> variables prescribed by the <span class="caps">WSGI</span> specification.
|
||
The server takes the values for the dictionary from the <span class="caps">HTTP</span> request after parsing the request.
|
||
This is what the contents of the dictionary look like:</p>
|
||
<p><img alt="Environ Python Dictionary" src="lsbaws_part2_environ.png" width="720"></p>
|
||
<p>A Web framework uses the information from that dictionary to decide which view to use based on the specified route, request method etc., where to read the request body from and where to write errors, if any.</p>
|
||
<p>By now you’ve created your own <span class="caps">WSGI</span> Web server and you’ve made Web applications written with different Web frameworks. And, you’ve also created your barebones Web application/Web framework along the way. It’s been a heck of a journey. Let’s recap what your <span class="caps">WSGI</span> Web server has to do to serve requests aimed at a <span class="caps">WSGI</span> application:</p>
|
||
<ul>
|
||
<li>First, the server starts and loads an <em>‘application’</em> callable provided by your Web framework/application</li>
|
||
<li>Then, the server reads a request</li>
|
||
<li>Then, the server parses it</li>
|
||
<li>Then, it builds an <em>‘environ’</em> dictionary using the request data</li>
|
||
<li>Then, it calls the <em>‘application’</em> callable with the <em>‘environ’</em> dictionary and a <em>‘start_response’</em> callable as parameters and gets back a response body.</li>
|
||
<li>Then, the server constructs an <span class="caps">HTTP</span> response using the data returned by the call to the <em>‘application’</em> object and the status and response headers set by the <em>‘start_response’</em> callable.</li>
|
||
<li>And finally, the server transmits the <span class="caps">HTTP</span> response back to the client</li>
|
||
</ul>
|
||
<p><img alt="Server Summary" src="lsbaws_part2_server_summary.png" width="700"></p>
|
||
<p>That’s about all there is to it. You now have a working <span class="caps">WSGI</span> server that can serve basic Web applications written with <span class="caps">WSGI</span> compliant Web frameworks like <a href="https://www.djangoproject.com/" title="Django">Django</a>, <a href="http://flask.pocoo.org/" title="Flask">Flask</a>, <a href="http://trypyramid.com/" title="Pyramid">Pyramid</a>, or your very own <span class="caps">WSGI</span> framework. The best part is that the server can be used with multiple Web frameworks without any changes to the server code base. Not bad at all.</p>
|
||
<p>Before you go, here is another question for you to think about, <em>“How do you make your server handle more than one request at a time?”</em></p>
|
||
<p>Stay tuned and I will show you a way to do that in <a href="../lsbaws-part3/index.html">Part 3</a>. Cheers!</p>
|
||
<p><br/>
|
||
<em>Resources used in preparation for this article (some links are affiliate links):</em></p>
|
||
<ol>
|
||
<li>
|
||
<p><a href="http://www.amazon.com/gp/product/0131411551/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0131411551&linkCode=as2&tag=russblo0b-20&linkId=2F4NYRBND566JJQL">Unix Network Programming, Volume 1: The Sockets Networking <span class="caps">API</span> (3rd Edition)</a><img src="http://ir-na.amazon-adsystem.com/e/ir?t=russblo0b-20&l=as2&o=1&a=0131411551" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="http://www.amazon.com/gp/product/0321637739/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321637739&linkCode=as2&tag=russblo0b-20&linkId=3ZYAKB537G6TM22J">Advanced Programming in the <span class="caps">UNIX</span> Environment, 3rd Edition</a><img src="http://ir-na.amazon-adsystem.com/e/ir?t=russblo0b-20&l=as2&o=1&a=0321637739" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="http://www.amazon.com/gp/product/1593272200/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1593272200&linkCode=as2&tag=russblo0b-20&linkId=CHFOMNYXN35I2MON">The Linux Programming Interface: A Linux and <span class="caps">UNIX</span> System Programming Handbook</a><img src="http://ir-na.amazon-adsystem.com/e/ir?t=russblo0b-20&l=as2&o=1&a=1593272200" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://www.python.org/dev/peps/pep-0333/" target="_blank"><span class="caps">PEP</span> 333 — Python Web Server Gateway Interface</a></p>
|
||
</li>
|
||
</ol>
|
||
<p><br/></p>
|
||
<blockquote>
|
||
<p><em><strong><span class="caps">UPDATE</span>: Mon, July 15, 2019</strong></em></p>
|
||
<ul>
|
||
<li>
|
||
<p>Updated the server code to run under Python 3.7+</p>
|
||
</li>
|
||
<li>
|
||
<p>Added resources used in preparation for the article</p>
|
||
</li>
|
||
</ul>
|
||
</blockquote>
|
||
<p><br/>
|
||
<p>If you want to get my newest articles in your inbox, then enter your email address below and click "Get Updates!"</p>
|
||
|
||
<!-- Begin MailChimp Signup Form -->
|
||
<link href="https://cdn-images.mailchimp.com/embedcode/classic-081711.css"
|
||
rel="stylesheet" type="text/css">
|
||
<style type="text/css">
|
||
#mc_embed_signup {
|
||
background: #f5f5f5;
|
||
clear: left;
|
||
font: 18px Helvetica,Arial,sans-serif;
|
||
}
|
||
|
||
#mc_embed_signup form {
|
||
text-align: center;
|
||
padding: 20px 0 10px 3%;
|
||
}
|
||
|
||
#mc_embed_signup .mc-field-group input {
|
||
display: inline;
|
||
width: 40%;
|
||
}
|
||
|
||
#mc_embed_signup div.response {
|
||
width: 100%;
|
||
}
|
||
</style>
|
||
<div id="mc_embed_signup">
|
||
<form
|
||
action="https://ruslanspivak.us4.list-manage.com/subscribe/post?u=7dde30eedc045f4670430c25f&id=6f69f44e03"
|
||
method="post"
|
||
id="mc-embedded-subscribe-form"
|
||
name="mc-embedded-subscribe-form"
|
||
class="validate"
|
||
target="_blank" novalidate>
|
||
<div id="mc_embed_signup_scroll">
|
||
|
||
<div class="mc-field-group">
|
||
<label for="mce-NAME">Enter Your First Name *</label>
|
||
<input type="text" value="" name="NAME" class="required" id="mce-NAME">
|
||
</div>
|
||
<div class="mc-field-group">
|
||
<label for="mce-EMAIL">Enter Your Best Email *</label>
|
||
<input type="email" value="" name="EMAIL" class="required email" id="mce-EMAIL">
|
||
</div>
|
||
<div id="mce-responses" class="clear">
|
||
<div class="response" id="mce-error-response" style="display:none"></div>
|
||
<div class="response" id="mce-success-response" style="display:none"></div>
|
||
</div>
|
||
<!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
|
||
<div style="position: absolute; left: -5000px;"><input type="text" name="b_7dde30eedc045f4670430c25f_6f69f44e03" tabindex="-1" value=""></div>
|
||
<div class="clear"><input type="submit" value="Get Updates!" name="subscribe" id="mc-embedded-subscribe" class="button" style="background-color: rgb(63, 146, 236);"></div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<!-- <script type='text/javascript' src='//s3.amazonaws.com/downloads.mailchimp.com/js/mc-validate.js'></script><script type='text/javascript'>(function($) {window.fnames = new Array(); window.ftypes = new Array();fnames[1]='NAME';ftypes[1]='text';fnames[0]='EMAIL';ftypes[0]='email';}(jQuery));var $mcj = jQuery.noConflict(true);</script> -->
|
||
<!--End mc_embed_signup-->
|
||
</p>
|
||
<p><br/>
|
||
<strong>All articles in this series:</strong></p>
|
||
<ul>
|
||
<li><a href="../lsbaws-part1/index.html">Let’s Build A Web Server. Part 1.</a></li>
|
||
<li><a href="index.html">Let’s Build A Web Server. Part 2.</a></li>
|
||
<li><a href="../lsbaws-part3/index.html">Let’s Build A Web Server. Part 3.</a></li>
|
||
</ul>
|
||
</div>
|
||
<!-- /.entry-content -->
|
||
<hr/>
|
||
<section class="comments" id="comments">
|
||
<h2>Comments</h2>
|
||
|
||
<div id="disqus_thread"></div>
|
||
<script type="text/javascript">
|
||
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
|
||
var disqus_shortname = 'ruslanspivak'; // required: replace example with your forum shortname
|
||
|
||
var disqus_identifier = 'lets-build-a-web-server-part-2';
|
||
var disqus_url = 'https://ruslanspivak.com/lsbaws-part2/';
|
||
|
||
var disqus_config = function () {
|
||
this.language = "en";
|
||
};
|
||
|
||
/* * * DON'T EDIT BELOW THIS LINE * * */
|
||
(function () {
|
||
var dsq = document.createElement('script');
|
||
dsq.type = 'text/javascript';
|
||
dsq.async = true;
|
||
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
|
||
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
|
||
})();
|
||
</script>
|
||
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by
|
||
Disqus.</a></noscript>
|
||
<a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
|
||
|
||
</section>
|
||
</article>
|
||
</section>
|
||
|
||
</div>
|
||
<div class="col-sm-3" id="sidebar">
|
||
<aside>
|
||
|
||
<section class="well well-sm">
|
||
<ul class="list-group list-group-flush">
|
||
<li class="list-group-item"><h4><i class="fa fa-home fa-lg"></i><span class="icon-label">Social</span></h4>
|
||
<ul class="list-group" id="social">
|
||
<li class="list-group-item"><a href="https://github.com/rspivak/"><i class="fa fa-github-square fa-lg"></i> github</a></li>
|
||
<li class="list-group-item"><a href="https://twitter.com/rspivak"><i class="fa fa-twitter-square fa-lg"></i> twitter</a></li>
|
||
<li class="list-group-item"><a href="https://linkedin.com/in/ruslanspivak/"><i class="fa fa-linkedin-square fa-lg"></i> linkedin</a></li>
|
||
</ul>
|
||
</li>
|
||
|
||
<li class="list-group-item"><h4><i class="fa fa-home fa-lg"></i><span class="icon-label">Popular posts</span></h4>
|
||
<ul class="list-group" id="popularposts">
|
||
<li class="list-group-item"
|
||
style="font-size: 15px; word-break: normal;">
|
||
<a href="../lsbaws-part1/index.html">
|
||
Let's Build A Web Server. Part 1.
|
||
</a>
|
||
</li>
|
||
<li class="list-group-item"
|
||
style="font-size: 15px; word-break: normal;">
|
||
<a href="../lsbasi-part1/index.html">
|
||
Let's Build A Simple Interpreter. Part 1.
|
||
</a>
|
||
</li>
|
||
<li class="list-group-item"
|
||
style="font-size: 15px; word-break: normal;">
|
||
<a href="index.html">
|
||
Let's Build A Web Server. Part 2.
|
||
</a>
|
||
</li>
|
||
<li class="list-group-item"
|
||
style="font-size: 15px; word-break: normal;">
|
||
<a href="../lsbaws-part3/index.html">
|
||
Let's Build A Web Server. Part 3.
|
||
</a>
|
||
</li>
|
||
<li class="list-group-item"
|
||
style="font-size: 15px; word-break: normal;">
|
||
<a href="../lsbasi-part2/index.html">
|
||
Let's Build A Simple Interpreter. Part 2.
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
|
||
<li class="list-group-item">
|
||
<h4>
|
||
<span>Disclaimer</span>
|
||
</h4>
|
||
<p id="disclaimer-text"> Some of the links on this site
|
||
have my Amazon referral id, which provides me with a small
|
||
commission for each sale. Thank you for your support.
|
||
</p>
|
||
</li>
|
||
|
||
|
||
|
||
</ul>
|
||
</section>
|
||
</aside>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<footer>
|
||
<div class="container">
|
||
<hr>
|
||
<div class="row">
|
||
<div class="col-xs-10">© 2020 Ruslan Spivak
|
||
<!-- · Powered by <a href="https://github.com/DandyDev/pelican-bootstrap3" target="_blank">pelican-bootstrap3</a>, -->
|
||
<!-- <a href="http://docs.getpelican.com/" target="_blank">Pelican</a>, -->
|
||
<!-- <a href="http://getbootstrap.com" target="_blank">Bootstrap</a> -->
|
||
<!-- -->
|
||
</div>
|
||
<div class="col-xs-2"><p class="pull-right"><i class="fa fa-arrow-up"></i> <a href="index.html#">Back to top</a></p></div>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
<script src="../theme/js/jquery.min.js"></script>
|
||
|
||
<!-- Include all compiled plugins (below), or include individual files as needed -->
|
||
<script src="../theme/js/bootstrap.min.js"></script>
|
||
|
||
<!-- Enable responsive features in IE8 with Respond.js (https://github.com/scottjehl/Respond) -->
|
||
<script src="../theme/js/respond.min.js"></script>
|
||
|
||
<!-- Disqus -->
|
||
<script type="text/javascript">
|
||
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
|
||
var disqus_shortname = 'ruslanspivak'; // required: replace example with your forum shortname
|
||
|
||
/* * * DON'T EDIT BELOW THIS LINE * * */
|
||
(function () {
|
||
var s = document.createElement('script');
|
||
s.async = true;
|
||
s.type = 'text/javascript';
|
||
s.src = '//' + disqus_shortname + '.disqus.com/count.js';
|
||
(document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s);
|
||
}());
|
||
</script>
|
||
<!-- End Disqus Code -->
|
||
<!-- Google Analytics Universal -->
|
||
<script type="text/javascript">
|
||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||
|
||
ga('create', 'UA-2572871-3', 'auto');
|
||
ga('send', 'pageview');
|
||
</script>
|
||
<!-- End Google Analytics Universal Code -->
|
||
|
||
</body>
|
||
</html> |