Piszemy własny serwer proxy w pythonie - cześć 1

Portret użytkownika bluszcz

Pisząc sceniaruszy testów obciążeniowych serwisów www często punktem wyjścia jest przygotowanie "tranzakcji" (używając tsungowej nomenklatury"), czyli listy plików dociąganych wraz z głównym dokumentem xhtml/html.

Na szczęście nowoczesne frameworki (między innymi Grinder, Tsung) dostarczają własne proxy umożliwiające przygotowanie takowej listy, często od razu w formacie używanym poprzez to narzędzie.

Co jednak zrobić w przypadku, kiedy potrzebujemy taką listę przygotować we własnym formacie, lub odrobinę zmodyfikowanym?

Wyjść mamy kilka, pierwszym najprostszym jest zainstalowanie dowolnego serwera proxy, między innymi squid lub tinyproxy. Squida nie polecam do tego celu - jest zdecydowanie zbyt potężny. Tinyproxy jest wygodny, małe i szybkie. Obydwa narzędzia przygotują piękną listę w formacie NCSA common log, który możemy łatwo sparsować...

Bardzo interesującym rozwiązaniem jest natomiast napisanie własnego proxy, dzięki czemu zyskamy:

  • nieocenioną wiedzę na temat protokołu HTTP :)
  • możliwość dowolnego formatowania requestów
  • możliwość dodania dowolnej funkcjonalności - na przykład liczenie objętości ściąganych plików za jednym żądaniem dokumentu html
  • i tak dalej...

Poniżej wklejam kawałek kodu, którego docstringi mówią wszystko - obsługuje on na początek jedynie metodę GET, łamie kilka standardów odnośnie protokołu HTTP - ale wszystko będzie w następnych częściach. Jako języka użyłem pythona - wydaje się on idealny, o zaletach nie będę się rozpisywał - poniższych kawałek kodu po odjęciu komentarzy zajmuje kilkanaście linijek i można go napisać w 30 minut...

#!/usr/bin/env python

"""
Simple HTTP Proxy - mainly used as additional to generate list of
downloaded additional statics files like css/js/imgs...
"""

from BaseHTTPServer import HTTPServer,BaseHTTPRequestHandler

### This is to use Threading module if Forking is not available
try:
    from SocketServer import ForkingMixIn as IdoruMixIn
except:
    from SocketServer import ThreadingMixIn as IdoruMixIn

import httplib

class IdoruHttpProxyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        """
        This one is for the HTTP GET method.
        When client (mostly browser) send GET requests we catch it here,
        and pass it to the destionation server, read the response
        and serve it back...
        """

        # headers dictionary for our proxy request
        headers = {}

        # we are copying here one dictionary to another, because in the
        # future we will ad some conditional on some headers to avoid
        # proxy them
        for header in self.headers.keys():
            headers[header] = self.headers[header]

        # creating connection for proxy request
        connection = httplib.HTTPConnection(headers['host'])

        # reuqesting the page from the destination server
        connection.request("GET", self.path, headers = headers)

        # creating the response object
        r1 = connection.getresponse()
        response_headers = r1.getheaders()

        # proxying status of the response
        self.send_response(r1.status)

        # copying dictionary, the reason to id is the same as above
        [ self.send_header(*x) for x in response_headers ]

        # reading response
        text = r1.read()

        # writing response back to browser
        self.wfile.write('\n')
        self.wfile.write(text)
        self.wfile.close()

        # closing connections
        connection.close()
        self.connection.close()

class IdoruHTTPServer(IdoruMixIn, HTTPServer):
    """ Idoru HTTP Server """
    pass

def main():
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option("-p", "--port", dest="port",
                      help="port on which iproxy will be listening", metavar="PORT",
                      default = 8000, type="int")
    parser.add_option("-i", "--ip-address", dest='ip',
                      help="address on which iproxy will be listening", metavar="IP",
                      default = '127.0.0.1')

    (options, args) = parser.parse_args()
    print "Staring iproxy on %s:%s, please hit C c to exit..." % (options.ip,options.port)
    httpd = IdoruHTTPServer((options.ip, options.port), IdoruHttpProxyHandler)
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        print "stopped..."
        httpd.server_close()
if __name__ == '__main__':
    # checking if we are run from the command line
    main()

Jako pracę domową, polecam dodanie obsługi metody HEAD :)

W następnej części zaimplementujemy metody HEAD i POST.

Bookmark and Share