2
\$\begingroup\$

Please help me improving this existing, working code. I am not asking you to change what it does, as it handles connections already perfectly as designed by my own specs. I just want the code to be reviewed to be sure.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os, ssl
from socketserver import ThreadingMixIn
from http.server import SimpleHTTPRequestHandler, HTTPServer

global sslcontext
sslcontext = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
sslcontext.options |= ssl.OP_NO_TLSv1
sslcontext.options |= ssl.OP_NO_TLSv1_1
sslcontext.protocol = ssl.PROTOCOL_TLSv1_2
sslcontext.set_ciphers("ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305")
sslcontext.set_ecdh_curve("secp384r1")
sslcontext.load_cert_chain("/home/pi/keys/fullchain.pem", "/home/pi/keys/privkey.pem")

class HSTSHandler(SimpleHTTPRequestHandler):
    def end_headers(self):
        self.send_header("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload")
        SimpleHTTPRequestHandler.end_headers(self)

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    daemon_threads = True

def main():
    try:
        os.chdir("/media/external")#auto-change working directory
        SimpleHTTPRequestHandler.sys_version = "0.1"#display custom Python system version
        SimpleHTTPRequestHandler.server_version = "0.2"#display custom server software version
        my_server = ThreadedHTTPServer(('', 443), HSTSHandler)
        my_server.socket = sslcontext.wrap_socket(my_server.socket, server_side=True)
        print('Starting server, use <Ctrl-C> to stop')
        my_server.serve_forever()

    except KeyboardInterrupt:
        print(' received, shutting down server')
        my_server.shutdown()

if __name__ == '__main__':
    main()
\$\endgroup\$
2
  • 1
    \$\begingroup\$ What does this actually do? How is /media/external significant? \$\endgroup\$ – Daniel Jul 2 '18 at 15:00
  • \$\begingroup\$ Can you tell us what /media/external contains? Does this work with arbitrary paths? I'm not saying this is a bad question, but it could use some extra information. \$\endgroup\$ – Daniel Jul 3 '18 at 11:03
1
\$\begingroup\$
  1. When importing names from a location (module, package, dynamic library, ...), don't mix multiple locations on a single line. That is, this:

    import os, ssl
    

    ... should be:

    import os
    import ssl
    

    This is purely a stylistic concern, and it's one of the rules listed in PEP-8, the official Python style guide.

  2. Global variables should be a last resort. They clutter the global namespace, are hard to track when reading the code, and are very prone to race conditions when using threads. Here, you could pass the context as an argument, or perhaps set up another class with the context attached as an attribute.

    Let's say you have to share foo between func_a() and func_b(). Three different approaches:

    • Make foo global (analogous to what you did with the context, not great):

      foo = Foo()
      
      def func_a():
          foo.method_1()
      
      def func_b():
          foo.method_2()
      
      func_a()
      func_b()
      
    • Pass foo as an argument:

      def func_a(foo):
          foo.method_1()
      
      def func_b(foo):
          foo.method_2()
      
      def main():
          foo = Foo()
          func_a(foo=foo)
          func_b(foo=foo)
      
    • Make foo a class attribute:

      class MyClass:
          def __init__(self, foo):
              self.foo = foo
      
          def func_a(self):
              self.foo.method_1()
      
          def func_b(self):
              self.foo.method_2()
      
      my_class = MyClass(Foo())
      my_class.func_a()
      my_class.func_b()
      

    Even if you stick to using global variables, setting global sslcontext is unnecessary because you're not reassigning it. If you're not assigning to a variable, Python will:

    i. Try to find the name locally;

    ii. If that fails, look for the name in global scope;

    iii. Finally, raise a NameError to indicate the variable doesn't exist in any scope.

    Therefore, if you reference sslcontext locally, the interpreter will find the global name (ii.). But it can't read your mind. If you instead assign sslcontext = 42, Python will assume you want to create a fresh, local variable with the name 'sslcontext' and the value of 42, rather than reassigning the global sslcontext. That's it. That's where global comes in, and that's all it's good for.

  3. In HSTSHandler.end_headers(), this idiom:

    SimpleHTTPRequestHandler.end_headers(self)
    

    ... could be more clearly expressed by calling super(), then super().end_headers(). This is good practice, and also assures multi-inheritance is handled correctly.

  4. Source code encoding does not need to be set explicitly here, as ASCII will suffice.

That's as much as I can give you without more context to the question.

\$\endgroup\$
2
  • \$\begingroup\$ @ran-sama I edited my answer and tried to explain in more detail what I meant. \$\endgroup\$ – Daniel Jul 13 '18 at 11:42
  • \$\begingroup\$ @ran-sama It's not wrong per se, rather an imprudent design choice. Remember that the tutorial pages often try to keep code snippets small and easy to read, not necessarily taking into account software design. \$\endgroup\$ – Daniel Jul 13 '18 at 13:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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