9
\$\begingroup\$

I was curious how hard it would be to implement a routing system like flask uses. As it turn's out it's actually not that hard if you understand how decorator's work.

The flask implementation has a lot of features like routes based on a regular expression. Mine just maps a path to a function.

#!/usr/bin/env python

from http.server import BaseHTTPRequestHandler, HTTPServer
import sys

DEFAULT_PORT = 8080
LOCALHOST = '127.0.0.1'
DEFAULT_ENCODING = 'utf-16'
ROUTES = None

class Routes(object):
    def __init__(self):
        self.routes = self.__dict__
        global ROUTES
        ROUTES = self

    def add(self, route, cb):
        self.routes[route] = cb()

    def get(self, route):
        return self.routes.get(route)

    def exists(self, route):
        return route in self.routes

    def __call__(self, route):
        return self.get(route)

class Route(object):
    def __init__(self, route):
        self.route = route

    def __call__(self, func):
        ROUTES.add(self.route, func)


class RequestHandler(BaseHTTPRequestHandler):
  Routes()
  def send_msg(self, msg, encoding=DEFAULT_ENCODING):
      self.wfile.write(bytes(msg, encoding))

  def do_GET(self):
      if not ROUTES.exists(self.path):
          self.send_header('content-type', 'text/html')
          self.send_response(404)
          self.end_headers()
          self.send_msg('<center><h1>404 😩</h1></center>')
          return 

      self.send_response(200)
      self.send_header('Content-type','text/html')
      self.end_headers()
      self.send_msg(ROUTES(self.path))
      return


def run(port=8080, server_addr=LOCALHOST):
  print('starting server...')
  server_address = (server_addr, port)
  httpd = HTTPServer(server_address, RequestHandler)
  print('running server...')
  httpd.serve_forever()

if __name__ == '__main__':
    @Route('/')
    def root():
        return '<h1><center><b>It Works 😁</b></center></h1>'
    run()
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

That's a nice mini-flask exercise.

ROUTES = None

Deferring the assignment to later, and initializing to Routes(), would seem more natural, to avoid having to test for is None.

    global ROUTES
    ROUTES = self
...
if __name__ == '__main__':
    @Route('/')

Oh, wait, you're not testing against None. You're just jamming it in there. Consider making the assignment the caller's responsibility.

    self.routes = self.__dict__

It's not obvious to me why you were reluctant to create a new dict(). Doesn't the current implementation preclude callers from creating routes to e.g. '/add' and '/get'?.

def add(self, route, cb):

I'm sure that's a very nice signature, and perhaps cb is the appropriate name to use. But it's obscure, and demands at least a docstring explaining it.

class Routes(object):
...
class Route(object):

Honestly, I'm not in love with the class names, there. It's not immediately obvious to the reader how a Routes is different from a Route, so distinguishing names with plural / singular probably isn't a win. Consider RouteCollection instead. But, better, consider deleting the Route class entirely, and using a method hanging off ROUTES to do the work instead. If you do retain two classes, you really really need to add a docstring to each one explaining what its Single Responsibility is.

class RequestHandler(BaseHTTPRequestHandler):
  Routes()

I suggest turning the ctor call into an assignment: ROUTES = Routes(). Better, make the assignment the responsibility of the "if __name__ is main" clause.

  self.send_msg(ROUTES(self.path))

Clearly this works. But it is less obvious than it might be. Consider ignoring __call__, in favor of boring syntax like ROUTES.get_document(self.path).

      self.send_msg('<center><h1>404 😩</h1></center>')

This is correct.

def root():
    return '<h1><center><b>It Works 😁</b></center></h1>'

I like the root identifier. But the HTML is incorrect, as <center> belongs outside of <h1>. Consider using CSS style= to replace that center.

run()

Please run flake8 and follow its advice to precede this line with two blank lines, setting it off from the decorated root function.

Overall, it's an interesting piece of code, I'm glad you shared it.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.