From 17933a9ae450936c3b9e8c52c92c488a1c40dac9 Mon Sep 17 00:00:00 2001 From: Greg Wilson Date: Tue, 27 May 2014 12:15:07 -0400 Subject: [PATCH] Updating based on feedback --- web-server/01-echo-request-info/server.py | 14 +- .../02-serve-static/server-status-code.py | 21 ++- web-server/02-serve-static/server.py | 21 ++- web-server/03-handlers/server-index-page.py | 21 ++- .../03-handlers/server-no-index-page.py | 31 ++-- web-server/03-handlers/server.py | 21 ++- web-server/04-cgi/server.py | 31 ++-- web-server/04-cgi/simple.py | 4 +- web-server/05-refactored/server.py | 31 ++-- web-server/05-refactored/simple.py | 4 +- web-server/chapter.md | 154 ++++++++++-------- 11 files changed, 184 insertions(+), 169 deletions(-) diff --git a/web-server/01-echo-request-info/server.py b/web-server/01-echo-request-info/server.py index a8b465967..f18f7bc48 100644 --- a/web-server/01-echo-request-info/server.py +++ b/web-server/01-echo-request-info/server.py @@ -10,12 +10,12 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): - - - - - - + + + + + +
Header Value
Date and time %(date_time)s
Client host %(client_host)s
Client port %(client_port)s
Command %(command)s
Path %(path)s
Header Value
Date and time {date_time}
Client host {client_host}
Client port {client_port}s
Command {command}
Path {path}
@@ -36,7 +36,7 @@ def create_page(self): 'command' : self.command, 'path' : self.path } - page = self.Page % values + page = self.Page.format(**values) return page # Send the created page. diff --git a/web-server/02-serve-static/server-status-code.py b/web-server/02-serve-static/server-status-code.py index 1f7354bba..f34d899f9 100644 --- a/web-server/02-serve-static/server-status-code.py +++ b/web-server/02-serve-static/server-status-code.py @@ -18,8 +18,8 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): Error_Page = """\ -

Error accessing %(path)s

-

%(msg)s

+

Error accessing {path}

+

{msg}

""" @@ -33,7 +33,7 @@ def do_GET(self): # It doesn't exist... if not os.path.exists(full_path): - raise ServerException("'%s' not found" % self.path) + raise ServerException("'{0}' not found".format(self.path)) # ...it's a file... elif os.path.isfile(full_path): @@ -41,25 +41,24 @@ def do_GET(self): # ...it's something we don't handle. else: - raise ServerException("Unknown object '%s'" % self.path) + raise ServerException("Unknown object '{0}'".format(self.path)) # Handle errors. - except Exception, msg: + except Exception as msg: self.handle_error(msg) def handle_file(self, full_path): try: - with open(full_path, 'r') as input: - content = input.read() + with open(full_path, 'rb') as reader: + content = reader.read() self.send_content(content) - except IOError, msg: - msg = "'%s' cannot be read: %s" % (self.path, msg) + except IOError as msg: + msg = "'{0}' cannot be read: {1}".format(self.path, msg) self.handle_error(msg) # Handle unknown objects. def handle_error(self, msg): - content = self.Error_Page % {'path' : self.path, - 'msg' : msg} + content = self.Error_Page.format(path=self.path, msg=msg) self.send_content(content, 404) # Send actual content. diff --git a/web-server/02-serve-static/server.py b/web-server/02-serve-static/server.py index 319027248..65c3df8ac 100644 --- a/web-server/02-serve-static/server.py +++ b/web-server/02-serve-static/server.py @@ -18,8 +18,8 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): Error_Page = """\ -

Error accessing %(path)s

-

%(msg)s

+

Error accessing {path}

+

{msg}

""" @@ -33,7 +33,7 @@ def do_GET(self): # It doesn't exist... if not os.path.exists(full_path): - raise ServerException("'%s' not found" % self.path) + raise ServerException("'{0}' not found".format(self.path)) # ...it's a file... elif os.path.isfile(full_path): @@ -41,25 +41,24 @@ def do_GET(self): # ...it's something we don't handle. else: - raise ServerException("Unknown object '%s'" % self.path) + raise ServerException("Unknown object '{0}'".format(self.path)) # Handle errors. - except Exception, msg: + except Exception as msg: self.handle_error(msg) def handle_file(self, full_path): try: - with open(full_path, 'r') as input: - content = input.read() + with open(full_path, 'rb') as reader: + content = reader.read() self.send_content(content) - except IOError, msg: - msg = "'%s' cannot be read: %s" % (self.path, msg) + except IOError as msg: + msg = "'{0}' cannot be read: {1}".format(self.path, msg) self.handle_error(msg) # Handle unknown objects. def handle_error(self, msg): - content = self.Error_Page % {'path' : self.path, - 'msg' : msg} + content = self.Error_Page.format(path=self.path, msg=msg) self.send_content(content) # Send actual content. diff --git a/web-server/03-handlers/server-index-page.py b/web-server/03-handlers/server-index-page.py index f51145cf1..f74055af3 100644 --- a/web-server/03-handlers/server-index-page.py +++ b/web-server/03-handlers/server-index-page.py @@ -15,7 +15,7 @@ def test(self, handler): return not os.path.exists(handler.full_path) def act(self, handler): - raise ServerException("'%s' not found" % handler.path) + raise ServerException("'{0}' not found".format(handler.path)) #------------------------------------------------------------------------------- @@ -52,7 +52,7 @@ def test(self, handler): return True def act(self, handler): - raise ServerException("Unknown object '%s'" % handler.path) + raise ServerException("Unknown object '{0}'".format(handler.path)) #------------------------------------------------------------------------------- @@ -71,8 +71,8 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): Error_Page = """\ -

Error accessing %(path)s

-

%(msg)s

+

Error accessing {path}

+

{msg}

""" @@ -91,22 +91,21 @@ def do_GET(self): break # Handle errors. - except Exception, msg: + except Exception as msg: self.handle_error(msg) def handle_file(self, full_path): try: - with open(full_path, 'r') as input: - content = input.read() + with open(full_path, 'rb') as reader: + content = reader.read() self.send_content(content) - except IOError, msg: - msg = "'%s' cannot be read: %s" % (self.path, msg) + except IOError as msg: + msg = "'{0}' cannot be read: {1}".format(self.path, msg) self.handle_error(msg) # Handle unknown objects. def handle_error(self, msg): - content = self.Error_Page % {'path' : self.path, - 'msg' : msg} + content = self.Error_Page.format(path=self.path, msg=msg) self.send_content(content, 404) # Send actual content. diff --git a/web-server/03-handlers/server-no-index-page.py b/web-server/03-handlers/server-no-index-page.py index 991af8fda..4851b08de 100644 --- a/web-server/03-handlers/server-no-index-page.py +++ b/web-server/03-handlers/server-no-index-page.py @@ -15,7 +15,7 @@ def test(self, handler): return not os.path.exists(handler.full_path) def act(self, handler): - raise ServerException("'%s' not found" % handler.path) + raise ServerException("'{0}' not found".format(handler.path)) #------------------------------------------------------------------------------- @@ -67,7 +67,7 @@ def test(self, handler): return True def act(self, handler): - raise ServerException("Unknown object '%s'" % handler.path) + raise ServerException("Unknown object '{0}'".format(handler.path)) #------------------------------------------------------------------------------- @@ -87,8 +87,8 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): Error_Page = """\ -

Error accessing %(path)s

-

%(msg)s

+

Error accessing {path}

+

{msg}

""" @@ -98,7 +98,7 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): @@ -118,32 +118,31 @@ def do_GET(self): break # Handle errors. - except Exception, msg: + except Exception as msg: self.handle_error(msg) def handle_file(self, full_path): try: - with open(full_path, 'r') as input: - content = input.read() + with open(full_path, 'rb') as reader: + content = reader.read() self.send_content(content) - except IOError, msg: - msg = "'%s' cannot be read: %s" % (self.path, msg) + except IOError as msg: + msg = "'{0}' cannot be read: {1}".format(self.path, msg) self.handle_error(msg) def list_dir(self, full_path): try: entries = os.listdir(full_path) - bullets = ['
  • %s
  • ' % e for e in entries if not e.startswith('.')] - page = self.Listing_Page % '\n'.join(bullets) + bullets = ['
  • {0}
  • '.format(e) for e in entries if not e.startswith('.')] + page = self.Listing_Page.format('\n'.join(bullets)) self.send_content(page) - except OSError, msg: - msg = "'%s' cannot be listed: %s" % (self.path, msg) + except OSError as msg: + msg = "'{0}' cannot be listed: {1}".format(self.path, msg) self.handle_error(msg) # Handle unknown objects. def handle_error(self, msg): - content = self.Error_Page % {'path' : self.path, - 'msg' : msg} + content = self.Error_Page.format(path=self.path, msg=msg) self.send_content(content, 404) # Send actual content. diff --git a/web-server/03-handlers/server.py b/web-server/03-handlers/server.py index c04e1eab5..c41402f01 100644 --- a/web-server/03-handlers/server.py +++ b/web-server/03-handlers/server.py @@ -15,7 +15,7 @@ def test(self, handler): return not os.path.exists(handler.full_path) def act(self, handler): - raise ServerException("'%s' not found" % handler.path) + raise ServerException("'{0}' not found".format(handler.path)) #------------------------------------------------------------------------------- @@ -37,7 +37,7 @@ def test(self, handler): return True def act(self, handler): - raise ServerException("Unknown object '%s'" % handler.path) + raise ServerException("Unknown object '{0}'".format(handler.path)) #------------------------------------------------------------------------------- @@ -55,8 +55,8 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): Error_Page = """\ -

    Error accessing %(path)s

    -

    %(msg)s

    +

    Error accessing {path}

    +

    {msg}

    """ @@ -75,22 +75,21 @@ def do_GET(self): break # Handle errors. - except Exception, msg: + except Exception as msg: self.handle_error(msg) def handle_file(self, full_path): try: - with open(full_path, 'r') as input: - content = input.read() + with open(full_path, 'rb') as reader: + content = reader.read() self.send_content(content) - except IOError, msg: - msg = "'%s' cannot be read: %s" % (self.path, msg) + except IOError as msg: + msg = "'{0}' cannot be read: {1}".format(self.path, msg) self.handle_error(msg) # Handle unknown objects. def handle_error(self, msg): - content = self.Error_Page % {'path' : self.path, - 'msg' : msg} + content = self.Error_Page.format(path=self.path, msg=msg) self.send_content(content, 404) # Send actual content. diff --git a/web-server/04-cgi/server.py b/web-server/04-cgi/server.py index 47f7576fb..8dda3f7c8 100644 --- a/web-server/04-cgi/server.py +++ b/web-server/04-cgi/server.py @@ -15,7 +15,7 @@ def test(self, handler): return not os.path.exists(handler.full_path) def act(self, handler): - raise ServerException("'%s' not found" % handler.path) + raise ServerException("'{0}' not found".format(handler.path)) #------------------------------------------------------------------------------- @@ -79,7 +79,7 @@ def test(self, handler): return True def act(self, handler): - raise ServerException("Unknown object '%s'" % handler.path) + raise ServerException("Unknown object '{0}'".format(handler.path)) #------------------------------------------------------------------------------- @@ -100,8 +100,8 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): Error_Page = """\ -

    Error accessing %(path)s

    -

    %(msg)s

    +

    Error accessing {path}

    +

    {msg}

    """ @@ -111,7 +111,7 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): @@ -131,26 +131,26 @@ def do_GET(self): break # Handle errors. - except Exception, msg: + except Exception as msg: self.handle_error(msg) def handle_file(self, full_path): try: - with open(full_path, 'r') as input: - content = input.read() + with open(full_path, 'rb') as reader: + content = reader.read() self.send_content(content) - except IOError, msg: - msg = "'%s' cannot be read: %s" % (self.path, msg) + except IOError as msg: + msg = "'{0}' cannot be read: {1}".format(self.path, msg) self.handle_error(msg) def list_dir(self, full_path): try: entries = os.listdir(full_path) - bullets = ['
  • %s
  • ' % e for e in entries if not e.startswith('.')] - page = self.Listing_Page % '\n'.join(bullets) + bullets = ['
  • {0}
  • '.format(e) for e in entries if not e.startswith('.')] + page = self.Listing_Page.format('\n'.join(bullets)) self.send_content(page) - except OSError, msg: - msg = "'%s' cannot be listed: %s" % (self.path, msg) + except OSError as msg: + msg = "'{0}' cannot be listed: {1}".format(self.path, msg) self.handle_error(msg) def run_cgi(self, full_path): @@ -163,8 +163,7 @@ def run_cgi(self, full_path): # Handle unknown objects. def handle_error(self, msg): - content = self.Error_Page % {'path' : self.path, - 'msg' : msg} + content = self.Error_Page.format(path=self.path, msg=msg) self.send_content(content, 404) # Send actual content. diff --git a/web-server/04-cgi/simple.py b/web-server/04-cgi/simple.py index a76988d99..be002a2c5 100644 --- a/web-server/04-cgi/simple.py +++ b/web-server/04-cgi/simple.py @@ -2,6 +2,6 @@ print '''\ -

    Generated %s

    +

    Generated {0}

    -''' % datetime.now() +'''.format(datetime.now()) diff --git a/web-server/05-refactored/server.py b/web-server/05-refactored/server.py index 4ad6c1152..4c3974e16 100644 --- a/web-server/05-refactored/server.py +++ b/web-server/05-refactored/server.py @@ -13,11 +13,11 @@ class base_case(object): def handle_file(self, handler, full_path): try: - with open(full_path, 'r') as input: - content = input.read() + with open(full_path, 'rb') as reader: + content = reader.read() handler.send_content(content) - except IOError, msg: - msg = "'%s' cannot be read: %s" % (full_path, msg) + except IOError as msg: + msg = "'{0}' cannot be read: {1}".format(full_path, msg) handler.handle_error(msg) def index_path(self, handler): @@ -38,7 +38,7 @@ def test(self, handler): return not os.path.exists(handler.full_path) def act(self, handler): - raise ServerException("'%s' not found" % handler.path) + raise ServerException("'{0}' not found".format(handler.path)) #------------------------------------------------------------------------------- @@ -93,7 +93,7 @@ class case_directory_no_index_file(base_case): @@ -102,11 +102,11 @@ class case_directory_no_index_file(base_case): def list_dir(self, handler, full_path): try: entries = os.listdir(full_path) - bullets = ['
  • %s
  • ' % e for e in entries if not e.startswith('.')] - page = self.Listing_Page % '\n'.join(bullets) + bullets = ['
  • {0}
  • '.format(e) for e in entries if not e.startswith('.')] + page = self.Listing_Page.format('\n'.join(bullets)) handler.send_content(page) - except OSError, msg: - msg = "'%s' cannot be listed: %s" % (self.path, msg) + except OSError as msg: + msg = "'{0}' cannot be listed: {1}".format(self.path, msg) handler.handle_error(msg) def test(self, handler): @@ -125,7 +125,7 @@ def test(self, handler): return True def act(self, handler): - raise ServerException("Unknown object '%s'" % handler.path) + raise ServerException("Unknown object '{0}'".format(handler.path)) #------------------------------------------------------------------------------- @@ -146,8 +146,8 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): Error_Page = """\ -

    Error accessing %(path)s

    -

    %(msg)s

    +

    Error accessing {path}

    +

    {msg}

    """ @@ -166,13 +166,12 @@ def do_GET(self): break # Handle errors. - except Exception, msg: + except Exception as msg: self.handle_error(msg) # Handle unknown objects. def handle_error(self, msg): - content = self.Error_Page % {'path' : self.path, - 'msg' : msg} + content = self.Error_Page.format(path=self.path, msg=msg) self.send_content(content, 404) # Send actual content. diff --git a/web-server/05-refactored/simple.py b/web-server/05-refactored/simple.py index a76988d99..be002a2c5 100644 --- a/web-server/05-refactored/simple.py +++ b/web-server/05-refactored/simple.py @@ -2,6 +2,6 @@ print '''\ -

    Generated %s

    +

    Generated {0}

    -''' % datetime.now() +'''.format(datetime.now()) diff --git a/web-server/chapter.md b/web-server/chapter.md index 45119f46d..1acf7eccc 100644 --- a/web-server/chapter.md +++ b/web-server/chapter.md @@ -9,15 +9,19 @@ a quarter of a century ago; in particular, most web servers still handle the same kinds of messages they did then, in the same way. -This chapter will explore how they do that, -and why. + +This chapter will explore how they do that. +At the same time, +it will explore how developers can create software systems +that don't need to be recompiled, +much less rewritten, +in order to add new capabilities. ## Background Pretty much every program on the web runs on a family of communication standards called Internet Protocol (IP). -IP is built in layers; -the one that concerns us is the Transmission Control Protocol (TCP/IP), +The particular member of that family which concerns us is the Transmission Control Protocol (TCP/IP), which makes communication between computers looks like reading and writing files. Programs using IP communicate through sockets. @@ -46,9 +50,12 @@ and the server sends some data in response. The data may be copied from a file on disk, generated dynamically by a program, or some mix of the two. -In all cases, + +The most important thing about an HTTP request is that it's just text: +any program that wants to can create one or parse one. +In order to be understood, though, -an HTTP request has the same parts: +that text must have the following parts: FIXME: diagram @@ -87,9 +94,6 @@ forgetting it is a common mistake. One header, called `Content-Length`, tells the server how many bytes to expect to read in the body of the request. -There's no magic in any of this: -an HTTP request is just text, -and any program that wants to can create one or parse one. HTTP responses are formatted like HTTP requests: @@ -115,7 +119,7 @@ The status phrase repeats that information in a human-readable phrase like "OK" For the purposes of this chapter there are only two other things we need to know about HTTP -The first is that it is stateless: +The first is that it is *stateless*: each request is handled on its own, and the server doesn't remember anything between one request and the next. If an application wants to keep track of something like a user's identity, @@ -133,7 +137,8 @@ and sends it to her browser. Each time her browser sends the cookie back, the server uses it to look up information about what the user is doing. -The second is that a URL is often not enough on its own. +The second is that a URL can be supplemented with parameters +to provide even more information. For example, if we're using a search engine, we have to specify what our search terms are. @@ -158,7 +163,9 @@ and how to interpret them. Of course, if '?' and '&' are special characters, -there must be a way to escape them. +there must be a way to escape them, +just as there must be a way to put a double quote character inside a character string +delimited by double quotes. The URL encoding standard represents special characters using '%' followed by a 2-digit code, and replaces spaces with the '+' character. @@ -326,7 +333,7 @@ included in the HTTP request. (We'll do this pretty frequently when debugging, so we might as well get some practice.) To keep our code clean, -we'l separate creating the page from sending it: +we'll separate creating the page from sending it: ~~~ {file="01-echo-request-info/server.py"} class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): @@ -364,11 +371,13 @@ with some formatting placeholders: - - - - - + + + + + + +
    Date and time %(date_time)s
    Client host %(client_host)s
    Client port %(client_port)s
    Command %(command)s
    Path %(path)s
    Header Value
    Date and time {date_time}
    Client host {client_host}
    Client port {client_port}s
    Command {command}
    Path {path}
    ''' @@ -385,7 +394,7 @@ and the method that fills this in is: 'command' : self.command, 'path' : self.path } - page = self.Page % values + page = self.Page.format(**values) return page ~~~ @@ -398,8 +407,6 @@ If we run it and send a request from a browser for `http://localhost:8080/something.html`, we get: - Header Value - ------ ----- Date and time Mon, 24 Feb 2014 17:17:12 GMT Client host 127.0.0.1 Client port 54548 @@ -408,10 +415,11 @@ we get: Notice that we do *not* get a 404 error, even though the page `something.html` doesn't exist. -Our web server isn't doing anything with the URL but echo it; -in particular, -it isn't trying to look up an HTML file on disk -to send back to us. +That's because a web server is just a program, +and can do whatever it wants when it gets a request: +send back the file named in the previous request, +serve up a Wikipedia page chosen at random, +or whatever else we program it to. ## Serving Static Pages @@ -428,7 +436,7 @@ We'll start by rewriting `do_GET`: # It doesn't exist... if not os.path.exists(full_path): - raise ServerException("'%s' not found" % self.path) + raise ServerException("'{0}' not found".format(self.path)) # ...it's a file... elif os.path.isfile(full_path): @@ -436,10 +444,10 @@ We'll start by rewriting `do_GET`: # ...it's something we don't handle. else: - raise ServerException("Unknown object '%s'" % self.path) + raise ServerException("Unknown object '{0}'".format(self.path)) # Handle errors. - except Exception, msg: + except Exception as msg: self.handle_error(msg) ~~~ @@ -464,14 +472,20 @@ and uses our existing `send_content` to send it back to the client: ~~~ {file="02-serve-static/server.py"} def handle_file(self, full_path): try: - with open(full_path, 'r') as input: - content = input.read() + with open(full_path, 'rb') as reader: + content = reader.read() self.send_content(content) - except IOError, msg: - msg = "'%s' cannot be read: %s" % (self.path, msg) + except IOError as msg: + msg = "'{0}' cannot be read: {1}".format(self.path, msg) self.handle_error(msg) ~~~ +Note that we open the file in binary mode --- the 'b' in 'rb' --- so that +Python won't try to "help" us by altering byte sequences that look like a Windows line ending. +Note also that reading the whole file into memory when serving it is a bad idea in real life, +where the file might be several gigabytes of video data. +Handling that situation is outside the scope of this chapter... + To finish off this class, we need to write the error handling method and the template for the error reporting page: @@ -480,15 +494,14 @@ and the template for the error reporting page: Error_Page = """\ -

    Error accessing %(path)s

    -

    %(msg)s

    +

    Error accessing {path}

    +

    {msg}

    """ def handle_error(self, msg): - content = self.Error_Page % {'path' : self.path, - 'msg' : msg} + content = self.Error_Page.format(path=self.path, msg=msg) self.send_content(content) ~~~ @@ -506,8 +519,7 @@ we need to modify `handle_error` and `send_content` as follows: ~~~ {file="02-serve-static/server-status-code.py"} # Handle unknown objects. def handle_error(self, msg): - content = self.Error_Page % {'path' : self.path, - 'msg' : msg} + content = self.Error_Page.format(path=self.path, msg=msg) self.send_content(content, 404) # Send actual content. @@ -519,6 +531,17 @@ we need to modify `handle_error` and `send_content` as follows: self.wfile.write(content) ~~~ +Note that we don't raise `ServerException` when a file can't be found, +but generate an error page instead. +A `ServerException` is meant to signal an internal error in the server code, +i.e., +something that *we* got wrong. +The error page created by `handle_error`, +on the other hand, +appears when the *user* got something wrong, +i.e., +sent us the URL of a file that doesn't exist. + ## Listing Directories As our next step, @@ -528,8 +551,8 @@ We could even go one step further and have it look in that directory for an `index.html` file to display, and only show a listing of the directory's contents if that file is not present. -But building these rules into the web server would be a mistake, -since the end result would almost certainly be a long tangle of `if` statements +But building these rules into `do_GET` would be a mistake, +since the resulting method would be a long tangle of `if` statements controlling special behaviors. The right solution is to step back and solve the general problem, which is figuring out what to do with a URL. @@ -550,7 +573,7 @@ Here's a rewrite of the `do_GET` method: break # Handle errors. - except Exception, msg: + except Exception as msg: self.handle_error(msg) ~~~ @@ -580,7 +603,7 @@ class case_no_file(object): return not os.path.exists(handler.full_path) def act(self, handler): - raise ServerException("'%s' not found" % handler.path) + raise ServerException("'{0}' not found".format(handler.path)) class case_existing_file(object): '''File exists.''' @@ -598,7 +621,7 @@ class case_always_fail(object): return True def act(self, handler): - raise ServerException("Unknown object '%s'" % handler.path) + raise ServerException("Unknown object '{0}'".format(handler.path)) ~~~ and here's how we construct the list of case handlers @@ -711,7 +734,7 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): @@ -720,11 +743,11 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): def list_dir(self, full_path): try: entries = os.listdir(full_path) - bullets = ['
  • %s
  • ' % e for e in entries if not e.startswith('.')] - page = self.Listing_Page % '\n'.join(bullets) + bullets = ['
  • {0}
  • '.format(e) for e in entries if not e.startswith('.')] + page = self.Listing_Page.format('\n'.join(bullets)) self.send_content(page) - except OSError, msg: - msg = "'%s' cannot be listed: %s" % (self.path, msg) + except OSError as msg: + msg = "'{0}' cannot be listed: {1}".format(self.path, msg) self.handle_error(msg) ~~~ @@ -749,9 +772,9 @@ from datetime import datetime print '''\ -

    Generated %s

    +

    Generated {0}

    -''' % datetime.now() +'''.format(datetime.now()) ~~~ In order to get the web server to run this program for us, @@ -814,10 +837,9 @@ it's clear that these three methods don't really belong where they are: FIXME: feature diagram The fix is straightforward: -create a parent class for all our case handlers -and put methods there -(if they're shared by two or more case handlers) -in the particular case handler that uses them. +create a parent class for all our case handlers, +and move other methods to that class +if (and only if) they are shared by two or more handlers. When we're done, the `RequestHandler` class looks like this: @@ -835,8 +857,8 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): Error_Page = """\ -

    Error accessing %(path)s

    -

    %(msg)s

    +

    Error accessing {path}

    +

    {msg}

    """ @@ -855,13 +877,12 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): break # Handle errors. - except Exception, msg: + except Exception as msg: self.handle_error(msg) # Handle unknown objects. def handle_error(self, msg): - content = self.Error_Page % {'path' : self.path, - 'msg' : msg} + content = self.Error_Page.format(path=self.path, msg=msg) self.send_content(content, 404) # Send actual content. @@ -881,11 +902,11 @@ class base_case(object): def handle_file(self, handler, full_path): try: - with open(full_path, 'r') as input: - content = input.read() + with open(full_path, 'rb') as reader: + content = reader.read() handler.send_content(content) - except IOError, msg: - msg = "'%s' cannot be read: %s" % (full_path, msg) + except IOError as msg: + msg = "'{0}' cannot be read: {1}".format(full_path, msg) handler.handle_error(msg) def index_path(self, handler): @@ -912,13 +933,14 @@ class case_existing_file(base_case): self.handle_file(handler, handler.full_path) ~~~ +## Discussion + The feature diagram for the refactored code is: FIXME: feature diagram -## Discussion - -Our finished code exemplifies two important ideas. +The differences between it and our previous feature diagram +reflect two important ideas. The first is to think of a class as a collection of related services. `RequestHandler` and `case_base` don't make decisions or take actions; they provide tools that other classes can use to do those things.