Creating a Proxy Server in Python | Set 2

Python Methods and Functions | Regular Expressions

This tutorial has added some cool features to make it more useful.

  • Add Domain Blacklist . For example google.com, facebook.com. Create BLACKLIST_DOMAINS in our dict config. For now, just ignore / discard requests received for blacklisted domains. (Ideally, we should reply with a forbidden answer.)
     # Check if the host: port is blacklisted for i in range (0, len (config ['BLACKLIST_DOMAINS'])): if config [' BLACKLIST_DOMAINS'] [i] in url: conn.close () return 
  • To add host lock: let's say you might need to allow connections from a specific subnets or connections for a specific person. To add this, create a list of all allowed hosts. Since hosts can also be a subnet, add a regex to match IP addresses, specifically IPV4 addresses.  “IPv4 addresses are canonically represented in decimal notation, which consists of four decimal numbers, each in the range 0 to 255, separated by periods, for example, 172.16.254.1. Each part represents a group of 8 bits (octets) of the address. "
  • Using regular expressions to match valid IP addresses:
    • Create a new _ishostAllowed method in the Server class and use the fnmatch module for regular expression matching. Loop over all the regular expressions and allow the query if it matches any of them. If the client's address is not found to be part of any regular expression, please post a FORBIDDEN response. Again, skip this part of building the response for now.

Note: We will be creating a fully fledged custom web server in the next lessons, it will create a createResponse function to handle creating a generic response.

 def _ishostAllowed (self, host): "" "Check if host is allowed to access the content" " "for wildcard in config [' HOST_ALLOWED']: if fnmatch.fnmatch (host, wildcard): return True return False 

The default host match regex will be & # 39; * & # 39; to match all hosts. However, you can also use a regular expression of the form "192.168. * ". The server is currently processing requests but is not showing any messages, so we are not aware of the state of the server. Its messages should be logged to the console. To do this, use the logging module as it is thread safe. (the server is multi-threaded, if you remember.)

Import the module and set up its initial configuration.

 logging.basicConfig (level = logging.DEBUG, format = '[% (CurrentTime) -10s] (% (ThreadName) -10s)% (message) s',) 
  • Create a separate method that logs each message : pass it as an argument with additional data such as the stream name and current time to keep track of the logs. Also create a function that colors the logs so they look nice on STDOUT. 
    To achieve this, add a boolean in the COLORED_LOGGING configuration and create a new function that colors each message passed to it based on LOG_LEVEL.
 def log (self, log_level, client, msg ): "" "Log the messages to appropriate place" "" LoggerDict = {'CurrentTime': strftime ("% a,% d% b% Y% X ", localtime ()),' ThreadName': threading.currentThread ( ) .getName ()} if client == -1: # Main Thread formatedMSG = msg else: # Child threads or Request Threads formatedMSG = '{0}: {1} {2}' .format (client [0], client [1], msg) logging.debug ('% s', utils.colorizeLog (config [' COLORED_LOGGING'], log_level, formatedMSG), extra = LoggerDict) 
  • Create a new module ColorizePython.py: it contains a pycolors class that maintains a list of color codes. Divide this into another module to make the code modular and follow PEP8 standards.
 # ColorizePython.py class pycolors: HEADER = '33 [95m' OKBLUE =' 33 [94m' OKGREEN = ' 33 [92m' WARNING = '33 [93m' FAIL =' 33 [91m' ENDC = '33 [0m' # End color BOLD =' 33 [1m' UNDERLINE = '33 [4m' 

Module:

 import ColorizePython 

Method :

 def colorizeLog (shouldColorize, log_level, msg): ## Higher is the log_level in the log () ## argument, the lower is its priority. colorize_log = {"NORMAL": ColorizePython.pycolors.ENDC, "WARNING": ColorizePython.pycolors.WARNING, "SUCCESS": ColorizePython.pycolors.OKGREEN, "FAIL": ColorizePython.pycolors.FAIL, "RESET": ColorizePython.WARNING .ENDC} if shouldColorize.lower () == "true": if log_level in colorize_log: return colorize_log [str (log_level)] + msg + colorize_log ['RESET'] return colorize_log [" NORMAL "] + msg + colorize_log [" RESET "] return msg 
  • Since colorizeLog is not a server class function, it is created as a separate module called utils.py, which contains all the utilities that facilitate understanding the code, and putting that method into it.  Add appropriate log messages where necessary, especially when the server state changes.
  • Change the shutdown method on the server to close all running threads before exiting the application.  threading.enumerate () iterates over all running threads, so we don't need to list them. The behavior of the threading module is unexpected when we try to terminate main_thread. The official documentation also says:

“Join () raises a RuntimeError if an attempt is made to join the current thread, as this will result in a deadlock. Also the error was attaching () to the thread before it was started and trying to do that throws the same exception. ”

So, pass this accordingly. Here's the code for the same.

 def shutdown (self, signum, frame): "" "Handle the exiting server. Clean all traces" "" self.log ("WARNING", -1, 'Shutting down gracefully ... ') main_thread = threading.currentThread () # Wait for all clients to exit for t in threading.enumerate (): if t is main_thread: continue self.log (" FAIL ", -1,' joining ' + t.getName ()) t.join () self.serverSocket.close () sys.exit (0) 

If you have any comments / suggestions / questions, feel free to ask.



Get Solution for free from DataCamp guru