445 lines
No EOL
29 KiB
HTML
445 lines
No EOL
29 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 1. - 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="Out for a walk one day, a woman came across a construction site and saw three men working. She asked the first man, “What are you doing?” Annoyed by the question, the first man barked, “Can’t you see that I’m laying bricks?” Not satisfied with the answer, she …" />
|
||
|
||
<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 1."/>
|
||
<meta property="og:url" content="https://ruslanspivak.com/lsbaws-part1/"/>
|
||
<meta property="og:description" content="Out for a walk one day, a woman came across a construction site and saw three men working. She asked the first man, “What are you doing?” Annoyed by the question, the first man barked, “Can’t you see that I’m laying bricks?” Not satisfied with the answer, she …"/>
|
||
<meta property="article:published_time" content="2015-03-09" />
|
||
<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 1.">
|
||
Let’s Build A Web Server. Part 1.
|
||
</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-03-09T08:00:00-04:00"> Mon, March 09, 2015</time>
|
||
</span>
|
||
|
||
|
||
|
||
|
||
</footer><!-- /.post-info --> </div>
|
||
</div>
|
||
<p>Out for a walk one day, a woman came across a construction site and saw three men working.
|
||
She asked the first man, “What are you doing?”
|
||
Annoyed by the question, the first man barked, “Can’t you see that I’m laying bricks?” Not satisfied with the answer, she asked the second man what he was doing.
|
||
The second man answered, “I’m building a brick wall.” Then, turning his attention to the first man, he said, “Hey, you just passed the end of the wall. You need to take off that last brick.” Again not satisfied with the answer, she asked the third man what he was doing. And the man said to her while looking up in the sky, “I am building the biggest cathedral this world has ever known.” While he was standing there and looking up in the sky the other two men started arguing about the errant brick. The man turned to the first two men and said, “Hey guys, don’t worry about that brick. It’s an inside wall, it will get plastered over and no one will ever see that brick. Just move on to another layer.”</p>
|
||
<p>The moral of the story is that when you know the whole system and understand how different pieces fit together (bricks, walls, cathedral), you can identify and fix problems faster (errant brick).</p>
|
||
<p>What does it have to do with creating your own Web server from scratch?</p>
|
||
<p><strong>I believe to become a better developer you <span class="caps">MUST</span> get a better understanding of the underlying software systems you use on a daily basis and that includes programming languages, compilers and interpreters, databases and operating systems, web servers and web frameworks. And, to get a better and deeper understanding of those systems you <span class="caps">MUST</span> re-build them from scratch, brick by brick, wall by wall.</strong></p>
|
||
<p>Confucius put it this way:</p>
|
||
<blockquote>
|
||
<p><em><span class="dquo">“</span>I hear and I forget.”</em></p>
|
||
</blockquote>
|
||
<p><img alt="Hear" src="../lsbasi-part4/LSBAWS_confucius_hear.png" width="640"></p>
|
||
<blockquote>
|
||
<p><em><span class="dquo">“</span>I see and I remember.”</em></p>
|
||
</blockquote>
|
||
<p><img alt="See" src="../lsbasi-part4/LSBAWS_confucius_see.png" width="640"></p>
|
||
<blockquote>
|
||
<p><em><span class="dquo">“</span>I do and I understand.”</em></p>
|
||
</blockquote>
|
||
<p><img alt="Do" src="../lsbasi-part4/LSBAWS_confucius_do.png" width="640"></p>
|
||
<p>I hope at this point you’re convinced that it’s a good idea to start re-building different software systems to learn how they work.</p>
|
||
<p>In this three-part series I will show you how to build your own basic Web server. Let’s get started.</p>
|
||
<p>First things first, what is a Web server?</p>
|
||
<p><img alt="HTTP Request/Response" src="LSBAWS_HTTP_request_response.png"></p>
|
||
<p>In a nutshell it’s a networking server that sits on a physical server (oops, a server on a server) and waits for a client to send a request. When it receives a request, it generates a response and sends it back to the client. The communication between a client and a server happens using <span class="caps">HTTP</span> protocol. A client can be your browser or any other software that speaks <span class="caps">HTTP</span>.</p>
|
||
<p>What would a very simple implementation of a Web server look like?
|
||
Here is my take on it. The example is in Python (tested on Python3.7+) but even if you don’t know Python (it’s a very easy language to pick up, try it!) you still should be able to understand concepts from the code and explanations below:</p>
|
||
<div class="highlight"><pre><span></span><span class="c1"># Python3.7+</span>
|
||
<span class="kn">import</span> <span class="nn">socket</span>
|
||
|
||
<span class="n">HOST</span><span class="p">,</span> <span class="n">PORT</span> <span class="o">=</span> <span class="s1">''</span><span class="p">,</span> <span class="mi">8888</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="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</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="n">listen_socket</span><span class="o">.</span><span class="n">bind</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="n">listen_socket</span><span class="o">.</span><span class="n">listen</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
|
||
<span class="k">print</span><span class="p">(</span><span class="n">f</span><span class="s1">'Serving HTTP on port {PORT} ...'</span><span class="p">)</span>
|
||
<span class="k">while</span> <span class="bp">True</span><span class="p">:</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="n">request_data</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="k">print</span><span class="p">(</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="n">http_response</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">"""</span><span class="se">\</span>
|
||
<span class="s2">HTTP/1.1 200 OK</span>
|
||
|
||
<span class="s2">Hello, World!</span>
|
||
<span class="s2">"""</span>
|
||
<span class="n">client_connection</span><span class="o">.</span><span class="n">sendall</span><span class="p">(</span><span class="n">http_response</span><span class="p">)</span>
|
||
<span class="n">client_connection</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||
</pre></div>
|
||
|
||
|
||
<p>Save the above code as <em>webserver1.py</em> or download it directly from <a href="https://github.com/rspivak/lsbaws/blob/master/part1/webserver1.py" title="GitHub">GitHub</a> and run it on the command line like this</p>
|
||
<div class="highlight"><pre><span></span>$ python webserver1.py
|
||
Serving HTTP on port <span class="m">8888</span> …
|
||
</pre></div>
|
||
|
||
|
||
<p>Now type in the following <span class="caps">URL</span> in your Web browser’s address bar <a href="http://localhost:8888/hello" title="Hello">http://localhost:8888/hello</a>, hit Enter, and see magic in action. You should see <em>“Hello, World!”</em> displayed in your browser like this:</p>
|
||
<p><img alt="Browser "Hello, World!"" src="browser_hello_world.png"></p>
|
||
<p>Just do it, seriously. I will wait for you while you’re testing it.</p>
|
||
<p>Done? Great. Now let’s discuss how it all actually works.</p>
|
||
<p>First let’s start with the Web address you’ve entered. It’s called an <a href="http://en.wikipedia.org/wiki/Uniform_resource_locator"><span class="caps">URL</span></a> and here is its basic structure:</p>
|
||
<p><img alt="URL Structure" src="LSBAWS_URL_Web_address.png" width="480"></p>
|
||
<p>This is how you tell your browser the address of the Web server it needs to find and connect to and the page (path) on the server to fetch for you.
|
||
Before your browser can send a <span class="caps">HTTP</span> request though, it first needs to establish a <span class="caps">TCP</span> connection with the Web server. Then it sends an <span class="caps">HTTP</span> request over the <span class="caps">TCP</span> connection to the server and waits for the server to send an <span class="caps">HTTP</span> response back. And when your browser receives the response it displays it, in this case it displays “Hello, World!”</p>
|
||
<p>Let’s explore in more detail how the client and the server establish a <span class="caps">TCP</span> connection before sending <span class="caps">HTTP</span> requests and responses. To do that they both use so-called <em>sockets</em>. Instead of using a browser directly you are going to simulate your browser manually by using <em>telnet</em> on the command line.</p>
|
||
<p>On the same computer you’re running the Web server fire up a telnet session on the command line specifying a host to connect to <em>localhost</em> and the port to connect to <em>8888</em> and then press Enter:</p>
|
||
<div class="highlight"><pre><span></span>$ telnet localhost <span class="m">8888</span>
|
||
Trying <span class="m">127</span>.0.0.1 …
|
||
Connected to localhost.
|
||
</pre></div>
|
||
|
||
|
||
<p>At this point you’ve established a <span class="caps">TCP</span> connection with the server running on your local host and ready to send and receive <span class="caps">HTTP</span> messages. In the picture below you can see a standard procedure a server has to go through to be able to accept new <span class="caps">TCP</span> connections.
|
||
<img alt="Socket accept" src="LSBAWS_socket.png" width="780"></p>
|
||
<p>In the same telnet session type <strong><em><span class="caps">GET</span> /hello <span class="caps">HTTP</span>/1.1</em></strong> and hit Enter:</p>
|
||
<div class="highlight"><pre><span></span>$ telnet localhost <span class="m">8888</span>
|
||
Trying <span class="m">127</span>.0.0.1 …
|
||
Connected to localhost.
|
||
GET /hello HTTP/1.1
|
||
|
||
HTTP/1.1 <span class="m">200</span> OK
|
||
Hello, World!
|
||
</pre></div>
|
||
|
||
|
||
<p>You’ve just manually simulated your browser! You sent an <span class="caps">HTTP</span> request and got an <span class="caps">HTTP</span> response back. This is the basic structure of an <span class="caps">HTTP</span> request:</p>
|
||
<p><img alt="HTTP Request Aanatomy" src="LSBAWS_HTTP_request_anatomy.png" width="560"></p>
|
||
<p>The <span class="caps">HTTP</span> request consists of the line indicating the <span class="caps">HTTP</span> method (<strong><em><span class="caps">GET</span></em></strong>, because we are asking our server to return us something), the path <em>/hello</em> that indicates a <em>“page”</em> on the server we want and the protocol version.</p>
|
||
<p>For simplicity’s sake our Web server at this point completely ignores the above request line. You could just as well type in any garbage instead of <em>“<span class="caps">GET</span> /hello <span class="caps">HTTP</span>/1.1”</em> and you would still get back a <em>“Hello, World!”</em> response.</p>
|
||
<p>Once you’ve typed the request line and hit Enter the client sends the request to the server, the server reads the request line, prints it and returns the proper <span class="caps">HTTP</span> response.</p>
|
||
<p>Here is the <span class="caps">HTTP</span> response that the server sends back to your client (<em>telnet</em> in this case):
|
||
<img alt="HTTP Response Anatomy" src="LSBAWS_HTTP_response_anatomy.png" width="560"></p>
|
||
<p>Let’s dissect it. The response consists of a status line <em><span class="caps">HTTP</span>/1.1 200 <span class="caps">OK</span></em>, followed by a required empty line, and then the <span class="caps">HTTP</span> response body.</p>
|
||
<p>The response status line <em><span class="caps">HTTP</span>/1.1 200 <span class="caps">OK</span></em> consists of the <em><span class="caps">HTTP</span> Version</em>, the <em><span class="caps">HTTP</span> status code</em> and the <em><span class="caps">HTTP</span> status code reason</em> phrase <em><span class="caps">OK</span></em>. When the browser gets the response, it displays the body of the response and that’s why you see <em>“Hello, World!”</em> in your browser.</p>
|
||
<p>And that’s the basic model of how a Web server works. To sum it up: The Web server creates a listening socket and starts accepting new connections in a loop. The client initiates a <span class="caps">TCP</span> connection and, after successfully establishing it, the client sends an <span class="caps">HTTP</span> request to the server and the server responds with an <span class="caps">HTTP</span> response that gets displayed to the user. To establish a <span class="caps">TCP</span> connection both clients and servers use <em>sockets</em>.</p>
|
||
<p>Now you have a very basic working Web server that you can test with your browser or some other <span class="caps">HTTP</span> client. As you’ve seen and hopefully tried, you can also be a human <span class="caps">HTTP</span> client too, by using <em>telnet</em> and typing <span class="caps">HTTP</span> requests manually.</p>
|
||
<p>Here’s a question for you: “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?”</p>
|
||
<p>I will show you exactly how in <a href="../lsbaws-part2/index.html">Part 2</a> of the series. Stay tuned.</p>
|
||
<p><br/>
|
||
<em>Resources used in preparation for this article (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="http://www.amazon.com/gp/product/0814420303/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0814420303&linkCode=as2&tag=russblo0b-20&linkId=HY2LNXTSGPPFZ2EV">Lead with a Story</a><img src="http://ir-na.amazon-adsystem.com/e/ir?t=russblo0b-20&l=as2&o=1&a=0814420303" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></p>
|
||
</li>
|
||
</ol>
|
||
<p><br/></p>
|
||
<blockquote>
|
||
<p><em><strong><span class="caps">UPDATE</span>: Sat, July 13, 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="index.html">Let’s Build A Web Server. Part 1.</a></li>
|
||
<li><a href="../lsbaws-part2/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-1';
|
||
var disqus_url = 'https://ruslanspivak.com/lsbaws-part1/';
|
||
|
||
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="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="../lsbaws-part2/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> |